The iOS team of iFunny has gone from a completely ad-free model to using a variety of ad networks and formats in their popular entertainment app. In this article, we will discuss some of the less-apparent nuances of working with advertising SDKs that can affect the user experience and performance of your product and share the code that will help you fix them.
So, your company wants to display ads in their app. Such businesses are called publishers. Every publisher has its ad inventory, the total amount of ad spaces available. Advertising can come in many forms, from simple banners to native advertising (an ad element designed to match the content) to interstitial or full-screen advertising, such as video ads. Armed with your set of ad spaces, we will now explore ad network SDKs and learn how to set them up.
Different ad networks provide ads differently, which is why managing the process of serving ads requires using an ad mediator, a system that will help you call various ad networks and pick the ads with the highest revenue. You can use a third-party platform or write your mechanisms to do this. For example, initially, we used an external mediation solution, but after being faced with its discontinuation, we built everything in-house, which has its advantages.
There are three ways in which a mediator can get ads:
The waterfall method involves ordering ad networks from the highest- to the lowest-paying one and calling them in that order. If the first, higher-paying network, fails to find any advertising (content) at the requested price or returns an error, then we call the next, lower-paying one. And so on, until we have an ad to be served. With this method, we call each network separately, which can take a long time. Plus, it requires sending a lot of queries, which makes it resource-intensive.
The header bidding method allows to call ad networks and find out their rates — basically, the revenue for serving an ad — in parallel. After selecting the highest-paying ad, we serve it to the user. This method only sends out requests for a short time and does not stress the network or the device as much.
There is also another, hybrid method, which involves conducting a local auction and inserting the winning bid into the waterfall.
Most likely, you will need to use both the bidding and waterfall models. After all, either one can be adopted by an ad network.
So you implement an algorithm and can start monetizing your app! However, it turns out it is not that simple : ) Enabling advertising is just the beginning.
Under iOS, updates for ad SDKs are usually distributed with CocoaPods, simply because their developers are reluctant to switch to a newer solution. And while you may already have a fancy, modern dependency manager, you will still need to use this third-party mechanism as well. After all, packages from CocoaPods cannot be easily used in the Swift Package Manager, and vice versa.
While you can continue to maintain stable versions of the SDK, at some point, you will still be forced to update to be able to receive ads from some particular network.
Be prepared for the possibility that the new version of the SDK may change its method signatures or variables. Here is one of the problems that we have encountered. We use callbacks to notify the SDK about ad impressions and clicks, and the ad network uses the delegation pattern to subscribe to them in our code. The ad network had updated its methods, but our Obj-C adapter could not inform us about that. So after the SDK update, we lost the ability to notify ad networks on served ads through our analytics and only found the problem in production.
That is why we have a dedicated checklist for SDK updates.
This is just a small snippet of the checklist. Given the different formats and types of mediation supported by our app, the full list of checks takes up several screens in Confluence.
The QA team checks to see if the ads are working as before, we fix the discovered bugs, and only then do we roll out the update to our app in the AppStore.
Another concern related to updating an ad network SDK is that these updates may depend on another SDK.
An example of such a crash report taken from an issue in the Facebook iOS SDK repository
Here is a case from our practice. One time, an update to the FBAudienceNetwork ad SDK required updating the FBSDKCoreKit and FBSDKLoginKit transitive dependencies, which are used to log in with Facebook. Our app started to crash. We were forced to roll back the core and the advertising SDK, wait for it to be fixed by its developers, and re-release the app.
In such cases, rolling back and waiting for a stable version is basically all you can do. However, you should always inform your ad partners of any arising problems.
Sometimes a bug fix can even be named after you ; )
Not all partners will respond quickly and fix your issue in a timely manner — it all depends on how big the company that owns the network is and how loyal it is to you as a publisher. Sometimes tickets can stay open for months, and sometimes you will see an update with a solution to your problem after just a couple of days.
Sometimes, further problems with the battery can be caused by an external mediator. For example, a third-party ad engine responsible for displaying banner ads had an algorithm for checking banner visibility. To do this, it called JS code inside the ad’s WebView 100 times per second. This puts much stress on the CPU of the device.
_adPropertyUpdateTimer = [Timer timerWithTimeInterval:adPropertyUpdateTimerInterval target:self selector:[@selector](http://twitter.com/selector)(updateMRAIDProperties) repeats:YES runLoopMode:NSRunLoopCommonModes];
A code for starting a timer that executes a JS script 100 times per second. The call is made inside the updateMRAIDProperties function. The adPropertyUpdateTimerInterval constant was set to 0.01, meaning that the timer interval was 10 milliseconds.
We decided to make our own optimization by reducing the number of calls to 10 per second. By increasing the interval between calls to the JS code for checking the banner, we managed to prevent our users’ batteries from draining so quickly.
After integrating an ad network, you may encounter unexpected content behavior. For instance, downloading a video with sound. This is bound to make your users hate you and prompt them to leave negative reviews on the app marketplace. If this happens, it is almost impossible to determine what ads were served to a particular user.
This problem needs to be dealt with in a systematic manner. For example, we have studied an ad network engine written in JS, found ads with audio, and figured out how they were named. After that, we wrote a simple script that searched for the “media” method in the HTML code of ad creatives, scanned them, and blocked video and sound.
We now apply this script to all ads that use JS and WebView. It mutes the sound automatically but leaves the user the option to turn it on.
In some SDKs, ad views can intercept user taps. This may cause unpleasant incidents where the user wants to swipe past the ad, but their gesture is intercepted and interpreted as a tap. When that happens, the user may be taken to a third-party website or app marketplace against their will, causing us to lose them for a time, or even forever.
To solve this problem, we wrote a special wrapper that prevents user input from getting intercepted.
Perhaps this solution will be of use to you too!
Sometimes, an ad SDK may be integrated incorrectly. Considering that modern ads may include large elements such as videos, your app can run out of memory and crash.
Sometimes such problems can arise due to an oversight. For example, the documentation may state that you must destroy advertising objects after serving them to the user. If you fail to do so, it will continue to be stored in memory, forming a Retain Cycle with another object, which will eventually cause a memory leak.
When this happens, you need to look for objects in your ad SDKs, examine these objects using the memory graph built into Xcode, and debug them.
If your product is popular in multiple different regions, one of the big challenges is getting ads for pre-release versions of the app. The success of an ad request depends on the user’s location and data as well as on whether IDFA is enabled for them. The easiest option is to enable a VPN and emulate your desired location.
Usually, ad networks provide tools for testing their ad creatives. For example, a special boolean flag in an advertising SDK that, when enabled, will send you test ads. Or these can be API keys for running test ads, which you can generate in the advertising admin panel and pass to the app.
However, there is a possibility that you will not get any ads. For example, if you are using iOS simulators for debugging, ad networks may detect the simulator and mistake it for fraud. That is why we test everything on real devices.
Many countries have laws and regulations that establish requirements for how personal data is requested, stored, and shared. Ad networks can use and sell user data for targeting purposes. Given that personalized advertising is more effective, GDPR, CCPA, LGPD, and the like may affect you as well.
You must prepare the user for the fact that their data may be shared with a third party. This is how we do this:
check the user’s location
if the user is in a region that has adopted its own regulations (GDPR for Europe, LGPD for Brazil, etc.), we show them the appropriate consent screen — the privacy dialog.
An example of a privacy dialogue in our app.
In addition to the mandatory consent or refusal of the user to use IDFA, there are requirements to how this information is passed to ad networks. In your code, you need to generate a TC string in the TCF framework developed by IAB. It will encrypt the consent or refusal of the user to share their data with ad networks. This string will then be sent to the networks, letting them know if they can use the user’s data upon its decryption.
P.S. Want more content like this? Subscribe to our blog!