Have you encountered this error when upgrading to the latest CocoaPods (1.1.0), or sharing a library between your iOS App and your extension?
If yes, continue reading, as you might have encountered same issue as myself. I’ve recently had to upgrade a project to using Cocoapods 1.1.0. Things stopped compiling, and I had to investigate the root cause of the problem. It has to do with iOS App extensions, unavailable APIs and how fragile our tooling is ;).
- The first cause of error can be fixed by conditionally compiling with a macro. See example here
- It’s better if you define whole classes or API unavailable using
- If you had this error in a 3rd party library you’ll need it to be fixed by the author (see below)
- If you are a library author and need to have different code paths via preprocessor macros, read this thread, and follow the recommendation to create a separated subspec for an extension target
Unavailable API for App extensions
Since the introduction of Application extensions several years ago, Apple has marked some API as unavailable for these targets. For example,
or in Swift:
Apple is using a new macro,
NS_EXTENSION_UNAVAILABLE_IOS to mark API as unavailable. There’s a new setting on Xcode,
APPLICATION_EXTENSION_API_ONLY, and if set, the code will not compile if it contains a call to
sharedApplication. This setting is automatically enabled for extension targets so you get the error in your code when you are writing it.
Writing separate code for an App target and and extension target is not an issue. You just don’t use the unavailable API in the extension. But what about reusable libraries?
Libraries using unavailable API for extensions
You might be using several libraries, and some of these might be using unavailable API for extensions. Notably AFNetworking is one of these, check the sources. The developer of the library must take care of this in their code, and mark API that is unavailable for extensions because of the usage of restricted system API.
But then, there might be code that is executing a different code path when compiled against an extension, for example PinCache. If this is the case, the library is forcing you to decide between using unsafe API or not using it. You’ll need to define the designated macro in your project to achieve this. See an example here.
The problem comes with the integration with CocoaPods, as they (correctly) deduplicate targets. This means that you’ll compile the library once (say without unavailable API) and link it to your targets. But sometimes you want to compile with usage of unavailable APIs against your main app, and removing unavailable usage on the extension. If you want this, you’re out of luck as it’s not directly supported by Cocoapods.
Since Cocoapods 1.1.0, they improved integration with App extensions, and the generated project will enable the flag
APPLICATION_EXTENSION_API_ONLY for libraries linking against an extension target. This is correct, but then you’ll start seeing compilation errors that can be a bit puzzling. Bear in mind that the code will not compile even if you don’t use the offending API. The compiler just complains that there is code that uses unavailable API.
So you can’t compile, you can’t just disable all unavailable API for your main App target. What do you do? There is hope! See this discussion, and you’ll see neonacho suggests to use a subspec to duplicate the targets. This is a very practical solution, but it requires the library author to modify their podspec.
Let’s see an example for a specific library. I had to fork Snowplow (the library we’re using) and add the subspec. If you want to check the real changes, a PR is here. Now let’s work it out with a fictional example replicating what’s required. Say you’re owner of
1 2 3 4 5 6
You use unavailable API, so the code conditionally compiles some parts based on a preprocessor macro called
MYLIBRARY_APP_EXTENSIONS. We declare a subspec, called Core with all the code, but the flag off. We make that subspec the default one if user doesn’t specify one. Then we’ll declare an additional subspec, called AppExtension including all the code, but setting the preprocessor macro:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Then in your application Podfile you’ll link against Core in your main app target, and against AppExtension in your extension, like so:
1 2 3 4 5 6 7 8 9 10 11 12
That’s it! neonacho’s suggestion works very well and it’s kind of simple. Hopefully this writeup will help you find the solution to your problem (and understand it) if you ever face it. Kudos for the CocoaPods team to offer support on these issues. We’re always catching up with Apple after they break (again) Xcode.
A note about Swift
I’ve found an issue created for Swift, SR-1226, that is still unresolved and might cause you problems. It seems that as of now, marking API as unavailable for extensions in Swift still doesn’t let you compile for App extensions. So be aware of this limitation.