How to build your own iOS framework

  • Tutorial
Among the tasks of a mobile developer, in addition to the most common (writing, in fact, applications), such as creating sdk periodically appears.

Examples of such a task can be the creation of sdk using the REST API of any service (advertising, analytics, weather), a library of implementation of algorithms, image processing ... The list is almost unlimited.

Mistakes made in such a product are much more difficult to fix than when developing applications. In the case of the application, it is enough to update it in the AppStore, wait for the moderation and user updates to pass. In the case of sdk, the chain grows as an additional step - wait for the developer to update it.

For some types of tasks, the situation can be aggravated by the fact that not all application developers using our sdk can be quite qualified. In this regard, integration should cause a minimum of complexity. This is especially true for advertising sdk and analytics, as they are often embedded much later than the main stage of creating the application and not always by the original developer.

Any similar sdk usually consists of many components: a library, a test application, documentation, plugins for other tools. In this article I will talk about building a library in the form of a framework, some techniques and development features.

image


To begin with, a small remark about the problem to be solved. Our goal is to get the library in a format that requires a minimum of effort and knowledge during integration, and also has no side effects after it.
Such a solution could be, for example, CocoaPods, which, in particular, allows you to distribute packages with closed source code. There are many articles (for example, here or here ) that describe how to publish your libraries in CocoaPods, and we will probably not repeat ourselves.
For our sdk, designed for a wide range of developers, this distribution method is obviously not enough: some developers do not use it fundamentally ("what if they block github again?"), But some have not heard about it at all.
We focus on the sdk assembly in the framework format. All that a user will need for integration is to drag him to his project.

About iOS8 dynamic frameworks


With the release of iOS 8 and the corresponding version of the iOS SDK, a new type of target appeared when creating a project - a dynamic framework. Let's try to find out if it suits us.
The appearance of this type of target was due, first of all, to the introduction of extensions, with which the main application has to share common code and resources. The resulting dynamic libraries are similar to the real ones, which are present, for example, in OS X only partially. The fact is that the library still remains in the application sandbox and can only be used for extensions and the main application.
Is it possible to use such frameworks to distribute our sdk? At the moment, the situation is ambiguous. From Apple documentationIt follows that the embedded framework can only be used on iOS8. An application may still have a Deployment Target below, but you will not be able to use such a framework. Practice shows a slightly different situation. Nevertheless, the Embedded framework can be launched on devices with an iOS version lower than 8, but this joy quickly disappears when trying to publish an application that uses such a framework in the AppStore. At the stage of automatic validation, an error is issued:
The MinimumOSVersion of framework "..." is invalid. The minimum value is iOS 8.0;

Setting the minimum version for the framework target to 8.0 leads, in turn, to an error at the project linking stage:
ld: embedded dylibs / frameworks are only supported on iOS 8.0 and later

A summary of the dynamic frameworks at the moment is this: you can use and distribute them, but only if the minimum version of the iOS SDK starts at 8.

About SDK assembly


Let's try to use the same dynamic framework template that Xcode 6 provides, but now to build its static version. It turns out that after some modifications this is quite possible. Let's get started.

We’ll create a new project where we will select Cocoa Touch Framework as the type of target. Choose any name. For definiteness, ours will be called MySDK.

image


Add the classes of our sdk to the project. Header files that should be publicly available are marked as Public and imported into the umbrella-header, which was created with the project by default. In our case, it is called MySDK.h

image


Next, go to the BuildSettings of our target and in the Linking section change Mach-O Type to Static Library.

image


In the settings of the scheme of our target, change the launch configuration to Release. This action is necessary in order for the assembly to take place for all available architectures, and not just for the active one.

image

A developer using our library will probably want to run code on all possible processor architectures for iOS. And we have to take care of this. Currently, the current list of architectures includes 3 for devices (armv7, armv7s, arm64) and 2 for simulators (i386, x86_64). To do this, you need to collect the so-called fat binary library, which will include layers for each of the supported architectures. To do this, create a target in the project of type Aggregate. Let's call it MySDKUniversal.

image


In the BuildPhases target, add the Run Script phase with the following script:

FRAMEWORK_NAME="${PROJECT_NAME}"
SIMULATOR_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${FRAMEWORK_NAME}.framework"
DEVICE_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphoneos/${FRAMEWORK_NAME}.framework"
UNIVERSAL_LIBRARY_DIR="${BUILD_DIR}/${CONFIGURATION}-iphoneuniversal"
FRAMEWORK_PATH="${UNIVERSAL_LIBRARY_DIR}/${FRAMEWORK_NAME}.framework"
xcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphonesimulator -target ${FRAMEWORK_NAME} -configuration ${CONFIGURATION} clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphonesimulator ARCHS='i386 x86_64'
xcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphoneos -target ${FRAMEWORK_NAME} -configuration ${CONFIGURATION} clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos ARCHS='arm64 armv7 armv7s'
rm -rf "${UNIVERSAL_LIBRARY_DIR}"
mkdir "${UNIVERSAL_LIBRARY_DIR}"
mkdir "${FRAMEWORK_PATH}"
cp -r "${DEVICE_LIBRARY_PATH}/." "${FRAMEWORK_PATH}"
lipo "${SIMULATOR_LIBRARY_PATH}/${FRAMEWORK_NAME}" "${DEVICE_LIBRARY_PATH}/${FRAMEWORK_NAME}" -create -output "${FRAMEWORK_PATH}/${FRAMEWORK_NAME}"

Here we call the assembly of our framework twice and collect the resulting libraries into one fat binary using the lipo utility. Note the last ARCHS parameter when calling xcodebuild. If you do not specify it, then the assembly for the device will use the list of architectures described in the variable ARCHS_STANDARD by default. The fact is that Apple, apparently, deliberately excluded armv7s for Xcode6 from the ARCHS_STANDARD variable, leaving only armv7, arm64. In general, this is not a big problem for the developer, and in the application, he can almost always exclude armv7s. The application without any problems, although probably with less optimization, will work on A6, A6X processors. But, when developing sdk, you should not take such a decision as a developer. It is much easier to list architectures explicitly.

About addictions


Rarely, what code can do without third-party dependencies. For example, network libraries, various parsers, categories to classes of system frameworks, etc. As a result, all these classes fall into our sdk, and the developer who uses it will be able to encounter problems when building his project. These problems will begin as soon as he wants to use one of those "wired" in sdk libraries. The compiler will produce a bunch of “duplicate symbols” when building its application. It would seem that this is bad: the developer just needs to remove this library from the dependencies, since its classes are already in our sdk. The catch is that it loses the ability to update this library and becomes dependent on the availability of our sdk.

The solution, which, with some reservations, can be called the best available, is to add prefixes to all imported symbols of the libraries used. Another step is added to the build process of our framework. To do this, create a separate project target, in which we will build a static library with all the dependencies. Let's call it ext.

image


Add all our dependencies to the CompileSources section of the build phase list. The last phase is adding a custom script. The script itself is taken here . Do not forget to fix the prefix that we will add to the characters from the library, and the path to the header file that we will generate. In our case, it is:

header="${SRCROOT}/${PROJECT_NAME}/ext/NamespacedDependencies.h"
prefix="MYSDK"


We start the assembly of our `ext` target. The resulting library we do not need. Of interest is the resulting header file NamespacedDependencies.h. Make sure that it is not empty and contains macros that replace the symbol names of our dependency library. After that, add the resulting header file to our main target MySDK.

NamespacedDependencies.h must be imported before importing any of the dependency headers. The most suitable place for this in the project is a PCH file. If it does not already exist in the project, then you need to create it. Do not forget to specify the path to it in the target settings.

image


Now, when building our framework, all the symbols from the dependency libraries will be provided with the specified prefix. This fact can be verified by running the nm utility.

nm -a /Users/mik/Library/Developer/Xcode/DerivedData/Build/Products/Release-iphoneuniversal/MySDK.framework/MySDK | grep MYSDK
00000000000022be t +[MYSDK_AFURLConnectionOperation batchOfRequestOperations:progressBlock:completionBlock:]
0000000000000000 t +[MYSDK_AFURLConnectionOperation networkRequestThreadEntryPoint:]
...


As a result of this trick, our sdk will not cause conflicts when using the same dependencies in the final project, although, of course, it will increase the size of the assembled application.

About resources


In addition to the compiled library itself, it is often necessary to distribute it along with resources: such as images, sounds, xib files, etc. And the framework structure can contain a folder with resources. However, Xcode simply ignores it.
In order to be able to add resources to your project, you need to distribute together with the framework either a separate bundle with resources or a symbolic link to resources located inside the framework itself.

Method 1

For resources, create a separate bundle, which we place inside the framework.

image


We will place all our resources in it, and it, in turn, is already inside the framework.
Due to the fact that we used the Bundle template for OS X, it is important not to forget to make a few more settings in the bundle target.
  1. install Base SDK on Latest iOS (default Latest OS X)
  2. remove Compile Sources section from Build Phases
  3. set Combine High Resolution Artwork setting to NO. Otherwise, resources for different screen densities will be collected in one TIFF file

After this setup, you can add MySDKResources.bundle to the Copy Bundle Resources section of our main MySDK target and the MySDKResources target in Target Dependencies.
It remains to add a symbolic link to the bundle located inside the framework. To do this, we add to the script that collects our universal line framework.

pushd ${UNIVERSAL_LIBRARY_DIR}
ln -sfh "${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}Resources.bundle" "${FRAMEWORK_NAME}Resources.bundle"
popd


Now to use sdk, in addition to the framework, you also need to add this symbolic link to the project.

Method 2

A significant disadvantage of the first method is that our framework ceases to be integral. And this is very significant in the distribution of sdk. For cases where this is applicable, you can use a technique such as embedding resources in sdk code. This method is convenient for storing small resources. For example, it can be sounds, images of buttons, logos or text resources. Naturally, the list is not limited to this set, the resources can be absolutely any.
The xxd utility comes to the rescue here, which has a wonderful output option in the format of a static C-array. I use such an additional resource generation script in the Build Phases project, which needs to be added as Run Script before Compile Sources.

RESOURCES_FILE="${SRCROOT}/${PROJECT_NAME}/MySDKResources.h"
rm -f ${RESOURCES_FILE}
pushd "${SRCROOT}/${PROJECT_NAME}Resources/images"
for filename in *.png; do
xxd -i $filename >> "${RESOURCES_FILE}"
done
popd


The resulting MySDKResources.h is added to the project. Then you can use the resources by creating an NSData object, and from it, in turn, any other object, for example, UIImage for images.

NSData *pngData = [NSData dataWithBytesNoCopy:close_png length:close_png_len freeWhenDone:NO];


Pro versioning


When distributing sdk, it is very convenient for each build to have a unique version number. Convenience is mainly noticeable when contacting support or when accounting for errors in the bugtracker. There are many ways to generate a version number. I settled on this. We use the format MAJOR.MINOR.BUILD, where MAJOR.MINOR is set manually depending on the release policy, backward version compatibility, etc. The number of commits in the current branch of the repository is taken as BUILD (release builds always occur from the same branch). To automate this process, a small script is used, which is added to the BuildPhases target before the build phase of the framework. After the first generation of this file, do not forget to add it to the project marked public and in our umbrella-header:

VERSION=`git rev-list HEAD --count`
echo "#define MYSDK_VERSION @\"1.0.${VERSION}\"" > ${SRCROOT}/MYSDKVersion.h

After that, the version number can be used by sdk itself. For example, as parameters of a request to the API or for output to the log.

Instead of a conclusion


So, in this article we were able to cover some important aspects of sdk development, mainly related to assembly. But on this the range of issues related to sdk is far from exhausted. Such interesting things as automation of sdk assembly, documentation, automation of sdk testing, support for older versions of iOS, development of plug-ins for tools such as Unity and Adobe Air remained unaffected. But this is already a topic for a separate and maybe not one article.

List of references

Also popular now: