This post has been republished via RSS; it originally appeared at: Microsoft Mobile Engineering - Medium.
For Microsoft SwiftKey Keyboard we use CocoaPods to manage most of our dependencies. Since iOS keyboard extensions run with strict memory and launch time limit, we are careful about what third party dependencies we pull into the project and how we link them. Using dynamic linking will typically slow down the cold launch time of the app, while static linking will increase the app size if the same dependency is linked to multiple targets and it could also cause issues because of duplicated symbols. Our rule of thumb has been to use static linking for dependencies linked to a single target and dynamic linking for the ones linked to multiple targets; this approach gives us good performances while avoiding to increase the app size unnecessary.
By default, CocoaPods will build pods as static libraries and the use_frameworks! attribute can be used to configure all pods in a target to be built as frameworks instead. Since the release of XCFrameworks, this attribute has been extended with the linkage option that allows to set the linkage style as static or dynamic.
This is a simple example of using the linkage option to mix dynamic and static linking in the same Podfile:
DynamicPod will be linked dynamically to DynamicTarget, while StaticPod will be linked statically to StaticTarget. Things will work great until you will need to add a new pod shared between those targets:
Again, DynamicPod will be linked dynamically and StaticPod will be linked statically, but what about SharedPod?
Since use_frameworks! can only be used to change the linkage style of all the pods in a target, SharedPod will be linked dynamically to DynamicTarget and statically to StaticTarget. CocoaPods will automatically two targets, SharedPod-dynamic and SharedPod-static, that will be linked to DynamicTarget and StaticTarget respectively.
This is fine if you don’t care about wasting space with two copies of the same frameworks and if the targets are unrelated to each other like an iOS app and a keyboard extension. If one of the targets import the other, then you will start facing issues because of the duplicated symbols.
At the moment, the latest version of CocoaPods (1.10.1) doesn’t have any way to set the linkage style of a single pod that would allow us to prevent this issue.
CocoaPods pod-linkage plugin
In SwiftKey, we have a dynamic framework that we use to share code between the app and the keyboard extension, and all the pods are linked statically except for some of them that are linked dynamically because they are linked to the app and keyboard extension too. To achieve that we created a CocoaPods plugin, cocoapods-pod-linkage, that adds support for the linkage option to individual pods.
For example, it can be used to fix the issue in the previous example by linking SharedPod dynamically to StaticTarget:
How to use the plugin ?
First of all, you need to install the plugin by adding to your Gemfile:
Then, you need to tell CocoaPods to load the plugin by adding to your Podfile:
Finally, you can start using :linkage => :static from static pods and :linkage => :dynamic for the dynamic ones. This option is not propagated to the pod’s dependencies automatically, therefore you will need to list explicitly all the pods that need a custom linkage option.
How the plugin works ?
To understand how the plugin works, it’s important to be familiar with what happen when you run pod install or pod update:
- Preparation: in this phase CocoaPods creates the installer and the sandbox (the Pods folder), loads all the plugins and give them an opportunity to run their pre_install hooks.
- Resolve dependencies: the analyzer loads and validates the Podfile, checks if the Podfile.lock is out to date, fetches the podspecs from extenal sources, and generates the list of targets and their dependencies.
- Download dependencies: the pods that need to be installed are downloaded and and the pre_install hook in the Podfile is run.
- Validate targets: all the targets are validated to ensure that there aren’t any issues like pods with unsupported Swift versions.
- Integrate: the Pods.xcodeproj is created and the Podfile’s post_install hook is run, then the workspace is created and the Podfile’s post_integrate hook is run.
- Write lockfile: the new Podfile.lock is generated and written to disk, then the plugins’s post_install hooks are run.
Since CocoaPods doesn’t offer a hook that can be used to modify the list of targets before the Pods.xcodeproj is created, the plugin overrides the analyzer to modify the targets generated in the “Resolve dependencies” phase.
The implementation looks like this:
generate_pod_targets is the Analyzer method responsible for computing the list of targets of the Podfile, and Ruby offers a simple way to override it using alias_method. After getting the original list of targets, we find the ones with an explicit linkage option and update their linking style.
The last missing piece is how to add the linkage option to pod. Each pod line in the Podfile calls store_pod for the current TargetDefinition and store_pod is responsible for parsing the pod’s configuration options. Since store_pod is called recursively, instead of overriding it to detect if a linkage option is present, we override parse_inhibit_warnings that is used to parse the inhibit_warnings option. This is the implementation:
How to contribute ?
The plugin has open sourced on GitHub, if you encounter a bug or you have an idea to improve it, please reach out and feel free to open a pull request!
Mixing static and dynamic linking in CocoaPods was originally published in Microsoft Mobile Engineering on Medium, where people are continuing the conversation by highlighting and responding to this story.