Adding a third party framework inside a first party framework in Xcode
By: Colin Chan
Tags: Libraries, Xcode, Modules, Framework, iOS

Building a fairly complex system from modular components has its many benefits and advantages, but doing so within the confines of Xcode for a fairly large IOS application can be tricky and un-obvious. This was the case recently where I had to implement and integrate an authentication module that depends on a third party module for its core functionalities.

Xcode utilizes a ‘framework’ concept to package up a well-defined collection of code and resources as a single library file for inclusion in a main project. Each framework should represent a separated feature that can individually be tested, built, shared, and reused. A main IOS project can explicitly include, replace, or delete frameworks as required, however a framework you create can depend on other third party frameworks.

The first party Authentication framework is dependent on third party frameworks, Auth0 and SimpleKeyChain. Creating and integrating the first party Authentication framework to the main app is relatively straightforward. But, instructing Xcode to nest or encapsulate the third party frameworks within its client is not obvious to do.

There are many online help to embed a framework with the main app. However, there are scant up-to-date information on how to setup dependency at a first and third party framework level using just XCode.

Prerequisite

Xcode 10, Swift 5

A dependency manager to download the third party libraries, e.g. CocoaPods

A custom framework target that will make use of the third party framework

How to make a Xcode project include CocoaPod libraries won’t be covered here. Once their libraries are installed they will be available in the overall project workspace. This is a limitation as ideally the third party libraries should be only visible within the first party framework.

Compiling and Building

If not set up right, integrating functionalities from a third party framework inside your framework module may cause compile time errors. These functionalities will be exposed as public interfaces, and they will need to be linked in to your code. Without knowing about the public interfaces the compiler will complain.

To access third party public functions, we normally import the third party framework module.

The error ‘No such module’ is a common compile time error. It notifies that the compiler was unable to locate the public header files for the third party module name being imported. To correct this, simply tell the compiler where they are. There are two important settings that will allow this.

1. Link Binary With Libraries

In Xcode, select the first party framework target, then in its Build Phase settings, add the ‘No such module’ to the ‘Links Binary with Libraries’ settings.

Note, unlike for a main app target, there is no ‘Embedded Binaries’ settings for a framework target.

The module names added to ‘Links Binary with Libraries’ setting will instruct the compiler to compile and build each of the given dependent module. It will look for their source code in the sub-root folder where CococaPods installs its pod modules, i.e. the third party libraries.

The compiled binary libraries, along with their public header files, will be copied over to the the ‘derived data folder’.

The ‘derived data folder’ is the default Xcode output folder for all compile and build operations for your project, including the main application code.

2. Framework Search Paths

The import error will still persist until this second step is added.

In Xcode, select the framework target, then add the following to the setting path Build Settings -> Search Paths -> Framework Search Paths

The $(BUILD_PRODUCTS_DIR) is a property that resolve to the build output folder, i.e. the derived data folder. These additions tells the compiler to look at the derived data folder to resolve the import references to the said third party libraries.

Linking

After successful compilation, running the main app which includes these levels of frameworks will generate loading errors if the linkage is not set up right. This commonly comes in the form of a ‘dyld: Library not loaded’ error message in the output log.

The compilation stage creates for each first and third party framework modules their binary representation. Those binary libraries don’t know the presence or location of each other.The dynamic linkage operation performs that task. It tells each library where, in memory space, to look for other libraries it depends on.

The error typically looks a bit like this:

Line by line, it can be interpreted as:

Amongst the list of paths searched by the dyld tool (dynamic linker), it couldn’t find a path to the third party library file SimpleKeychain.

The first party library Auth is dependent on the SimpleKeychain library.

The SimpleKeychain library was not loaded in memory, so dyld tried to load it by looking for its file location path.

To fix the problem the first interpretation needs to be resolved. The list of paths the linker searches for are declared in this framework’s target setting: Build Setting -> Linking -> Runpath Search Paths

Its settings value should open up a list of paths. This is where dyld looks for the location of libraries to load.

Here, in addition to the standard system libraries, append the dependent third party frameworks. The property $(BUILT_PRODUCTS_DIR) will resolve to the sub-root folder of where all libraries are compiled to, the derived data folder.

Summary

Eventually the application should run with all the inter-dependencies between the main app, first and third party frameworks configured in place.

Xcode does not provide any help or reason for many of it configuration settings. There is online help available but you have to trawl through scatterings to get the relevant answers related to nested framework dependencies.

Hopefully what I have gathered can help others who are looking for a resolution to similar problems, or provide understanding to further their investigation.