Analytics - How to avoid common mistakes in iOS

I have been interested in analytics tools for a while, especially when it’s applied to mobile development. Over the time, I saw many code mistakes when implementing an analytical solution. Some of them can be easily avoided when developer got the right insights, let’s see how.

Start with abstraction

The naive way to include a tracking tool is to literally apply the company’s documentation, often resumed to a drag & drop in your project and launch the SDK as soon as possible in the AppDelegate.m. This is an example from Facebook official documentation.

- (BOOL)application:(UIApplication *)application 
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  // You can skip this line if you have the latest version of the SDK installed
  [[FBSDKApplicationDelegate sharedInstance] application:application
  // Add any custom logic here.
  return YES;

Another example is when triggering those analytics tools, like Google Analytics, making a view directly dependent of their SDK.

@implementation ViewController

- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];

  NSString *name = [NSString stringWithFormat:@"Pattern~%@", self.title];
  id<GAITracker> tracker = [GAI sharedInstance].defaultTracker;
  [tracker set:kGAIScreenName value:name];
  [tracker send:[[GAIDictionaryBuilder createScreenView] build]];

For me, this is the very first issue that can weight your project down. When we want to add a new third party library to track user behavior, we tend to forget the best practice we try to follow and cut corners suggested by their documentation.

I believe that you should start think of how easy it it to remove those dependencies when you implement them. For sure, one day will come where you’ll have to remove it and abstracting the library avoid those strong dependencies across your project to it.

The reason why might change: maybe the contract ended, maybe your marketing team found a better one, maybe you built an internal one. Anyway, abstracting your analytics stack from day 1 will save you lot of trouble later.

A simple example would be to use a protocol oriented programming and use a wrapper around the third part to protect the core of your code to the dependency itself.

Ideally, it could look like this

class ViewController: UIViewController {

  var tracker: TrackingProtocol?

  override func viewWillAppear(_ animated: Bool) {
    tracker.send(.screenName, value:name)

You can read more about abstraction in swift.

Define your strategy

A common mistake is to want to track every user interactions. If that can sound a good plan but this approach won’t help you to define behavior: a user did a search, another add a product to cart, but how many products have been added from a search result? This flow might be missed if you are not careful.

One way to tackle this is to draw a simple funnel through your application. For an e-commerce application, from the first launch of your app, how many users browse products, add to cart and place an order? What about wishlist? etc.

If this sound far from the responsibility of an iOS developer, in practice it’s closer than you think. The implementation of your code depend of that funnel. For instance, Firebase/Google Analytics use custom dimensions to support those.

override func viewWillAppear(_ animated: Bool) {

  tracker.send(.screenName, value:name, params: ["customDimension1": "123"])

Keep it testable

One thing I noticed in the past is we often assume we can’t test third party libraries. Well, in practice, you can and you should.

It’s true that it can be tricky to actually unit test it, but if you decouple each layer, they can be testable separately.

For instance, assuming we send specific payload for tracker, we are still able to test that those are mapped the right way.

// 1 - define protocol for any tracker
protocol TrackingProtocol {
  func payload(for event: Event, params: [String:Any]) -> [String:Any]?

// 2 - implement specific logic to map payload
final class CustomTracker: TrackingProtocol {
  // ...

class CustomTrackingTests: XCTestCase {

  var tracker: CustomTracker?

  // 3 - test payload
  func testProductPayload() {

    // given a valid payload with custom dimension
    let payload = tracker.payload(for: .addToBag, params: ["product": Product(name:"Nike", price:100)])

    // expecting matching fields in final payload
    let expectedPayload = ["product_name": "Nike", "price": 100.0]
    XCTAssertEqual(payload, expectedPayload)

This is a simple example approach, but it already allows us to test the mapping between app objects to the expected payload by your third party. That’s also why it’s so important to keep your tracking tools separated to your core code.

On the other side, some third party already include testable environment. For instance, Firebase allows you to debug event on live for a given session.

I’ll cover more in debt about testability of tracking tools in the future. Stay tuned

Monitoring is key

Last advice I can think of is to make sure you keep your tracking metrics monitored. It sounds obvious like that but when adding a event or updating an implementation, make sure the numbers still match after release.

If you see numbers oddly going up or dropping without real reason, you might be looking at a false positive: maybe the event is duplicated in your funnel, maybe it got removed, etc.

It might not look like a big issue at first: the final user isn’t affected, right? However, if any teammates use those corrupted data to make create report and make new assessment, well, it might be the wrong one.

In conclusion, if implementing tracking tool can be really easy on the paper, as an mobile software engineer, it’s still our responsibility to be done following the best practices. Let’s not forget that any data driven strategy make sense only with the right data.

© 2023 Benoit Pasquier. All Rights Reserved
Author's picture

Benoit Pasquier

Software Engineer πŸ‡«πŸ‡·, writing about career development, mobile engineering and self-improvement

ShopBack πŸ’°

Singapore πŸ‡ΈπŸ‡¬