We get it. You’re tired of constantly updating your third party SDKs.

You need to update your SDKs for bug fixes.

You need to update your SDKs for new features.

You need to update your SDKs because undocumented changes in native APIs broke the SDKs’ functionality.

What’s that? You’re not as familiar with the third one?

By the end of this post, you will be. After all, sometimes it is not a choice made by the SDK developer. It is a choice that Apple made and forced the SDK developer to adapt to.

In this post, we’ll explain a bug that surfaced in Embrace’s iOS SDK when iOS 13 introduced undocumented changes to NSURLSession that affected how network requests were implemented internally. We’ll explain how the bug affected our SDK and what we did to fix it. Then we’ll end with a plea to write those unit tests. We know they are time-consuming, but tests are your best friend in situations where the code you write is subject to native APIs that can (and do) change without proper documentation.

How NSURLSession used to work (Pre-iOS 13)

NSURLSession is a framework provided by Apple to help developers create  and run networking tasks. For a deep dive, you can check out Apple’s documentation on it. To summarize, you can create Upload tasks to put a file on a server, Download tasks to down a download a file, WebSocket tasks for exchanging messages over TCP and TLS, and Data tasks to stream data from a server.

For our purposes here, we will focus on data tasks. There are four main ways to create a data task with NSURLSession:

  1. dataTaskWithRequest
  2. dataTaskWithURL
  3. dataTaskWithRequest with a completion handler
  4. dataTaskWithURL with a completion handler

The main difference between dataTaskWithRequest and dataTaskWithURL is that the former builds a request object that you can customize with things like headers. The latter only creates a URL string, and since it isn’t creating a request object, there’s very little a developer can do to customize the network call.

In other words, dataTaskWithRequest can do anything dataTaskWithURL can, but the same is not true the other way around.

As Embrace is a mobile monitoring and developer analytics platform, we need to hook into all available network request APIs on iOS so that regardless of the way a developer chooses to make requests, we can track them. In writing code to listen to all these various calls, we discovered an internal implementation detail of iOS. (Note: This was not documented by Apple. It was discovered by our engineers.)

// When somebody calls:
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
// Apple just calls 'dataTaskWithRequest' after creating a request for the user

In other words, dataTaskWithURL was actually just a wrapper around dataTaskWithRequest and automatically created a request object using the URL string passed in. This meant that Embrace only had to track dataTaskWithRequest methods to still monitor all network requests originating from data tasks in NSURLSession.

This simplified our SDK code by allowing us to address fewer methods while still covering all network requests that stem from data tasks.

How NSURLSession changed in iOS 13

iOS 13 changed the underlying implementation of NSURLSession so that dataTaskWithURL now takes a shortcut and doesn’t create an entire request object.
This change was not documented by Apple.

Embrace learned about this change only after seeing unit tests for dataTaskWithURL fail. The unit tests were failing because we couldn’t add a custom header to a request object that didn’t exist.

Embrace relies on adding an identifier in the form of a custom header to link requests to the appropriate response. Prior to iOS 13, on dataTaskWithURL, we could simply add the header to the resulting dataTaskWithRequest. But now that dataTaskWithURL wasn’t creating a request object, our code that added a header was failing.

We fixed the bug by wrapping all dataTaskWithURL calls with a dataTaskWithRequest. That way, we can add the custom header regardless of the way the developer implements the data task.

Summing up what we’ve learned

SDK developers sometimes release updates to fix broken functionality caused by changes to native APIs. Many times, this is unavoidable. Third-party SDKs build on top of native APIs that are subject to change, sometimes in undocumented ways.

The example we mentioned in iOS 13 NSURLSession was not documented by Apple in any way. At Embrace, we wrote extensive unit tests that informed us when our functionality started breaking.

So to summarize, write those unit tests.

And don’t always blame SDK developers for endless new versions. Sometimes, it’s Apple’s fault.

Embrace is a data driven toolset to help mobile engineers build better experiences. If you’d like to learn more about Embrace, you can check out our website or request a demo.