Create an iOS Universal Framework

Our technology is to create a 3D avatar from one single front face photo (http://getinsta3d.com). Recently, we would like to wrap this technology to let our partners easily integrate it and focus on creating more innovative and interesting apps. Therefore, we need to come out a way to deliver the technology without giving them the source code.

In 2014 WWDC, Apple Inc. announced Xcode is going to support framework for iOS platform. This is the first time ever, private framework is supported in the iOS platform. And some features, like extension, in iOS8 are required to be in the framework form. But why use framework? If you compare to static library solution, framework is an easier and more straightforward solution for our partners. With framework, we can wrap all the resource files, including images and 3D model assets in one single framework file (it is a directory actually). Our partners can simply drag and drop the framework file into their app project, and it is done. That is why we decided to go framework solution.

To pack a framework for iOS is easy. But the challenge is how to create an universal framework to be used in both real devices and simulators. I did some researches on Internet, and here I would like to share the steps we used in our project.


Create a Framework project

In Xcode 6, it is easy to create a framework project. Create a new project, and select “Cocoa Touch Framework” template in “Framework & Library”.

Once you create it, you should be able to see a framework target in your project file. Drag and drop all your source files in this project, and make it compile.

When you have your project built, you can find the framework binary under Xcode DerivedData directory. You can create a sample project, and drag and drop the framework file you just got into this sample project to test the functions.

When you test with your sample project, you will find the framework can only work on one of architectures, either simulator or real device, depending on the platform you are building the framework for.

So, we need to create an universal framework to let it works on both simulator and device.


Cocoapods

But wait, if your project uses some other 3rd parties libraries through Cocoapods, you probably know Cocoapods will install a workspace file for your project. But no worry, you still can use Cocoapods when you are building a framework.

In our case, we are using Cocoapods to link with AFNetworking. We created a Podfile, and execute “pod install” to install the pods. Then we get a workspace file, open the workspace file, and we do the following steps in the workspace.


Make it Universal

Now, we need to make it universal. Open your workspace file (if you like me are using Cocoapods in your project.), create a new “Aggregate” target, and name it <FrameworkName>-Universal.

In this new target, create a new “Run Script” in the “Build Phases”.

And then copy and paste the follow code snippets in the dialog.

https://gist.github.com/syshen/c24d127e1adc2783e0e7

In the code snippet above, you can find the following command:

“xcodebuild -workspace ${PROJECT_NAME}.xcworkspace -scheme ${PROJECT_NAME} -sdk iphonesimulator -configuration ${CONFIGURATION} clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphonesimulator”

And it is invoked twice with different SDKs, one is for simulator and another one is for device. And we are using xcodebuild command to build the workspace, not project (because we are also using Cocoapods.)

Framework is built twice with different SDKs, and the binaries are placed in different directory. So, we need to use lipo to combine these binary as one, which is the universal binary we need.

lipo “${SIMULATOR_LIBRARY_PATH}/${FRAMEWORK_NAME}” “${DEVICE_LIBRARY_PATH}/${FRAMEWORK_NAME}” -create -output “${FRAMEWORK}/${FRAMEWORK_NAME}”

At this moment, when you build it, you probably will have an error messages, saying that xcodebuild is not able to find the schemes. To fix this, you need to shared project’s scheme. Click “Product” on the Xcode menu, and then “Manage Schemes”. Check on the schemes you want to share. In this case, that is the “<FrameworkName>”.

Then every time, select the <FrameworkName>-Universal scheme and build, you will get the universal framework binary under the <ProjectDir>/Output/ directory (or you can simply change the output directory by your own)

And test it in your sample project.


CI Integration

We are using Travis as our CI environment. To make it built in Travis, simply write a Makefile to trigger the command:

xcodebuild -workspace FaceKit.xcworkspace -scheme <FrameworkName>-Universal -configuration Release clean build

(You need to make <FrameworkName>-Universal scheme as shared in your project scheme setting as well.)

And then configure your .travis.yml, commit to github, everything is done.

Trouble Shooting

  • Symbol not found in your simulator

Solution: Set No in your framework build setting “Build Active Architecture Only”, both Debug and Release. This setting is Yes by default for Debug configuration, and it will only build i386 architecture for iphone simulator. But in your app project, you will need x86_64 architecture. That is why you should turn off this setting.

  • Getting “image not found” error with your framework name:
dyld: Library not loaded: @rpath/UXTracking.framework/UXTracking
Referenced from: /Users/syshen/Library/Developer/CoreSimulator/Devices/6E4E02A1-0A91-43A6-A9F3-7B20749BE9C0/data/Containers/Bundle/Application/B314917D-42CA-4019-BB2F-71CE18B58C05/UXTracking.app/UXTracking
Reason: image not found

Solution: make sure you include your framework in “Embedded Binaries” section.

After you do so, xcode will copy and embed this framework in Frameworks directory inside your app.

  • Getting “image not found” error with system libraries or frameworks.

Solution: make sure you link with every libraries (frameworks) you used in your framework project setting.

  • Getting “dyld: Library not loaded: @rpath/libswiftCore.dylib” error while app is launched.

Solution: Turn on your app project build setting “Embedded Content Contains Swift Code”, make it as Yes. If not working, also make sure the “Runpath Search Paths” is set as “@executable_path/Frameworks”.

  • Support iOS7

If you build your framework with “deployment target” as iOS7, you probably will get this warning message “Embedded dylibs/frameworks only run on iOS8 or later”. You still can get the build, but with this warnning message. From some Q&A in stackoverflow, most people also have the same warning messages since XCode6 beta until GM. And based on my testing, even with this warning message, the framework can be loaded well in iOS7 without problem. But since this warning message, this is not guaranteed.

ps. Apple forbid the use of embedded framework in iOS7 or older versions. You will not be allowed to submit an app for review with an embedded framework in your app. Therefore, this solution only works in iOS8 only if you are going to release the app.

http://stackoverflow.com/questions/24622495/relocatable-dylibs-e-g-embedded-frameworks-are-only-supported-on-ios-8-0-and

ps. If you are looking for a sample, here is the source code: https://github.com/syshen/Embedded-Framework-Demo . It includes a swift framework project, and an Objective-C and a Swift App project.

Thanks for reading this article, any feedback is welcome, and recommend it if you think it is helpful. Please also visit our web site: http://getinsta3d.com and contact us.

Show your support

Clapping shows how much you appreciated Steven Shen’s story.