Apple’s iOS Software Development Kit (SDK) lets us build high-quality apps fast by providing a huge amount of functionality that would take us a really long time if needed to implement it ourselves. The iOS SDK contains a huge number of classes, methods and other interfaces that we can use in iOS apps written in Swift. Each of these may be referred to as an application programming interface (API), and all these APIs collectively form the iOS SDK’s API.
In this post, we’ll look at how Apple uses deprecation as a way to evolve its APIs over time. We’ll also see how we update our apps to follow along with Apple’s changes.
Apple’s API Evolution Strategy
Apple tries to keep its APIs stable if possible so that our apps that depend on them can keep working. However, software is an ever-evolving field, so Apple needs to carefully evolve its APIs to make improvements and keep up with the times. A major example is
UIWebView, which is an in-process web renderer. Apple created the alternative class
WKWebView, which loads and renders web content in a separate process. This provides security and performance benefits, so
WKWebView should be used instead of
UIWebView. Another example is after Apple added share and actions extensions, it was possible for Twitter to supply its own extension to enable users to post tweets from any app. Since Apple no longer needs to supply its own Twitter integration, the constant
SLServiceTypeTwitter is no longer needed.
If Apple released an update to iOS that immediately changed or removed an API, then our apps that used that API would crash on that iOS version unless we updated our apps right away. We call this a breaking API change. This would be annoying, to say the least. Many apps that aren’t actively maintained would stop working, and users would be very upset. Therefore, to avoid breaking API, Apple uses a concept called deprecation.
When an API is deprecated, it means Apple doesn’t recommend using that API, but the API is (almost always) still functional. Typically, this happens when a superior alternative API is available. The deprecation may mean that Apple intends to remove the API at some point in the future. The company would typically communicate this intention and provide a long transition period.
In almost all cases, deprecated APIs continue to work the same as they did before they were deprecated.
The timeline might look like this:
The API is introduced.
Apple realises there’s a problem with the API. It’s marked as deprecated but keeps working. A replacement API is introduced.
Later on, Apple announces the API will be removed.
Later still, the API is removed.
In many cases, the deprecated API will stay around for a very long time. We’re talking about a timescale where the iOS SDK is still too young for many APIs to have been removed. For example, many properties on
UITableViewCell — such as
text — were deprecated in iPhone OS 3.0 in 2009 and are still around today in 2021. That’s 12 years later!
The case of
WKWebView is interesting because migrating between these classes is very difficult for some apps. In total, this migration has been in progress for more than seven years:
WKWebViewwith iOS 8 (2014) as an alternative to
Over the next four years, Apple increasingly recommended
WKWebView, leading to the formal deprecation of
UIWebViewin iOS 12 (2018).
Apple is also using app review to push its API evolution. New apps using
UIWebViewstopped being accepted by app review in April 2020.
As of iOS 15 (2021),
UIWebViewis still present. Apple says updates to existing apps that still use
UIWebViewwill be rejected at some point, although the deadline for this has been extended and not confirmed as of October 2021.
An example of an API that was removed is
ARError.Code.collaborationDataUnavailable. This was added in iOS 13.0. One month later, in iOS 13.2, this error code was deprecated and
ARError.Code.invalidCollaborationData was added. I’d guess Apple decided the naming of the original API didn’t accurately reflect the situations where this error code was applicable. Two years later, in iOS 15,
.collaborationDataUnavailable was removed entirely. Presumably because this was only part of the stable and non-deprecated API for a month, Apple decided a two-year transition period was long enough to have minimum impact on production apps.
There are two key benefits to this deprecation window:
It gives us time to update our apps.
Since our apps often support older iOS versions where newer APIs don’t exist, it means we don’t need to have implementations using both the old API and the new API.
For example, picking photos using
UIImagePickerController.SourceType.photoLibrary was deprecated in iOS 14 alongside the introduction of
PHPickerViewController, which provides more flexibility and several other benefits. (Well, sort of: The documentation says the older API is deprecated, but the header file actually marks it as
API_TO_BE_DEPRECATED. In other words, it’ll be deprecated at some point in the future.) In any case, it’s clear that Apple recommends using
PHPickerViewController instead of
If our app still runs iOS 13, then we need to keep using
UIImagePickerController for iOS 13, even though
PHPickerViewController is a better option on iOS 14 and later. Rather than having conditional code, it may make more sense to keep using
UIImagePickerController on all iOS versions. However, if we stopped supporting iOS 13, then the best API to use would change to
PHPickerViewController. In the next section, we’ll look at precisely what I mean by supporting an iOS version.
What Does the Deployment Target Setting Do?
The deployment target project setting is the way we specify the oldest iOS version our app is allowed to run on.
iOS will refuse to run an app if that app’s deployment target is newer. For example, if you set your app’s deployment target to 16.0, then it won’t run on iOS 15. The App Store also prevents a user from downloading an app if the deployment target is too new.
The deployment target has no effect on your app when it runs on a supported iOS version.
For example, in iOS 14, the
requestReview() was deprecated, and the method
requestReview(in:) was added to allow a window scene to be specified, since iOS 14 added support for multiple app windows on iPad. Let’s say our app only supports one window, it uses the older
requestReview() API and its deployment target is iOS 13. There will be no warnings in this case.
If we change our deployment target from iOS 13 to iOS 14, then the app will run exactly the same as it did before on iOS 14 or later: The
requestReview() method is still available and functional. However, changing the deployment target does affect the warnings you’ll see when building your project. With a deployment target of iOS 14,
requestReview() is deprecated on all iOS versions our app supports. Therefore, Xcode will show deprecation warnings for use of
requestReview(). We should switch to
These are warnings — not errors — so we can ignore them and our app will keep working for now, but be aware that this is cutting into the deprecation window that Apple gave us, so this is a risky choice. If Apple decides to remove the API later, then we’ll have less time to plan and react to this.
There’s currently no way to disable deprecation warnings in Swift except by disabling all warnings using the Suppress Warnings setting (
SWIFT_SUPPRESS_WARNINGS), which is probably undesirable in most cases. There’s a Deprecated Functions setting (
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS) that’s listed in the Xcode build settings under Apple Clang > Warnings > All languages, but unfortunately, this only seems to apply to C-based languages, so it doesn’t work with Swift.
There are many benefits to moving from deprecated APIs to replacement APIs:
The app is likely to work longer into the future without needing updates.
This may allow us to simplify our code.
This may give us performance improvements.
This may let us benefit from enhancements from Apple ‘for free’ later on.
For these reasons, we at PSPDFKit plan for the long term and move away from deprecated APIs as soon as warnings start showing up.
What Does the Base SDK Setting Do?
The base SDK setting for an iOS app is more or less the highest iOS version you’ve tested your app on. This doesn’t prevent your app from running on future versions. In contrast to the deployment target, the base SDK does change your app’s behaviour when running. This is because Apple’s frameworks will try to behave as they did on the version matching the app’s base SDK to maintain compatibility.
For example, on iOS 15,
UITabBar started using a transparent background when the main content scroll view is scrolled to the bottom. Tab bars didn’t do this on iOS 14, so if an app’s base SDK is iOS 14, then UIKit won’t use transparent tab bar backgrounds. I’d presume that Apple’s frameworks contain huge numbers of these conditional code branches, and it seems likely that old bugs need to be replicated for compatibility! Working with this sounds immensely challenging, and I think Apple does a terrific job maintaining compatibility.
The base SDK is essentially tied to the Xcode version, and it’s necessary to use the latest Xcode version to use the latest APIs from Apple. You can see which SDK is included in each version of Xcode on Apple’s What’s Included in Xcode page. At PSPDFKit, it’s important to us to support the latest APIs — both to benefit from new system improvements, and to not hold our customers back. Therefore, we almost always require the latest stable Xcode version.
Deprecated APIs in PSPDFKit
Our own PSPDFKit APIs follow an API evolution approach similar to Apple’s.
For example, when we dropped support for iOS 12, we deprecated APIs relating to our page grabber, which is a UI component on the edge of the screen that the user could drag to scroll up and down between pages. We deprecated this because, since iOS 13,
UIScrollView has built-in support for allowing the user to drag the scroll indicator to scroll quickly. As of PSPDFKit 11 for iOS, our page grabber continues to function, but it’s redundant. Our own implementation of this feature made sense at the time we added it, but going forward, it makes more sense to use the system implementation to simplify our APIs and our codebase.
The key is to find the right balance between allowing improvements to the API and maintaining compatibility. It makes sense for us to be slightly more eager with removing deprecated APIs than Apple is. Our policy is to leave deprecated APIs present and functional for at least a year. Often, we’ll leave them longer if it’s not resulting in a maintenance burden for us. In certain cases, we’ll move more aggressively to modernise our APIs faster.
We mark an API as deprecated so that if customers use that API, they see clear compiler warnings. We highlight all deprecations and breaking changes in our changelog with red and yellow badges, and for notable changes, we walk though how to move to newer APIs in our migration guides. Our recent release of PSPDFKit 11 for iOS is a smooth update so that our customers can easily add iOS 15 support. As such, it has no breaking API changes, and no migration guide is required.
In this post, we’ve seen how the iOS SDK evolves using API deprecation. API evolution is a balance between improving APIs and maintaining compatibility for existing apps. The time between when an API is deprecated and when that API is removed is typically a year or more for PSPDFKit, and it’s many years for Apple.
An app’s deployment target can be increased to remove support for older iOS versions that don’t have much active use. Xcode will then show deprecation warnings as a hint for us, suggesting we migrate to newer replacement APIs. An app’s base SDK can be increased by updating to the later Xcode version to opt in to more modern system features and behaviour.