Sunday, July 06, 2014

Building iOS Cocoa Touch Framework using Swift

With the XCode 6, comes the ability to develop "Cocoa Touch Framework" which is probably (not sure about how the DLL hell translates on to iOS) a better way to package the solution AND is the only way to create libraries in swift. The "Cocoa Touch Static library" AFAIK does not support swift language.

So far I have not been able to find anything standard step by step for this which is uses swift. This is just a post to capture my learning on this.

Most of the process should be inline with that identified in good post and detail as answer to stackoverflow question. Some additional points to consider

Swift based Frameworks

Ensure that you have selected Swift as language while creating new project.

I have been able to successfully run the framework as part of swift application on iPhone 5s with OS 7.1 which seems to indicate that older deployment targets may be supported. So, feel free to experiment with older target version. Based on the initial test the obvious issue is going to be support for new APIs (one of them being the UIAlertController and UIAlertAction APIs for generating alerts - I could not find any way to support this in swift using older APIs)

I have not been able to deploy the Archived file (ipa) through itunes (the installation process gets stuck in "Installing" and never completes). Need more research on that front.

Extending NSObject

As part of defining the swift implementation the system will create a corresponding <project name>.h file. I was able to define the @interface for my code in objective-c but when I tried to reference the same in Swift code later, I got following error
'<Class Name>' is not constructible with '()'
This was resolved by extending the @interface with NSObject (Reference). Looks like XCode does not do a good job of cleaning cache on "Cleanup" because of which when I tested later with a framework with missing NSObject, the system does not trigger any error.

By the way, there was no need to implement/extends the NSObject in swift class (given swift does not support root object concept).

Building project

As indicated in the various links, you need to additional work to create a universal binary for releasing your framework. The initial link has steps associated with a clean way to lipo the two builds. Based on my experience I have updated the script and that is available here for reference
# define output folder environment variable
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal/${PROJECT_NAME}.framework
 
# Step 1. Build Device and Simulator versions
xcodebuild -target ${PROJECT_NAME} ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}"
xcodebuild -target ${PROJECT_NAME} -configuration ${CONFIGURATION} -sdk iphonesimulator ARCHS="x86_64" ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}"
 
# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
 
# copy all the framework files. Just for convenience
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework""${BUILD_DIR}/${CONFIGURATION}-universal/"
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework""${BUILD_DIR}/${CONFIGURATION}-universal/"
 
# Step 2. Create universal binary file using lipo
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}""${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}""${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}"
Besides some basic generalization and framework specific folder structure changes, the following changes have been made
  1. ARCHS="x86_64" ONLY_ACTIVE_ARCH=NO was added to build process for iphonesimulator to create binary that can be used on 64bit machine. I am not sure why this does not trigger by default in my environment given Mac has been 64 bit for a long time. Please note that if the build was performed using UI then build was automatically 64 bit.

    ignoring file <file name>, missing required architecture x86_64 in file

    Undefined symbols for architecture x86_64:
  2. Copy both simulator and iphoneos files. Looks like the "compiled" swift object files are created for each of the platforms and without them linking will fail.
    Undefined symbols for architecture x86_64:

Importing frameworks

Basically, I tried Linking frameworks but that did not work because the application at runtime will expect the framework at specific location i.e. ~/Library/Developer/ (as defined in build process). So, I Embedded Binaries into the project. This hopefully is also a better way to avoid DLL nightmare. 

By the way I did not have to create any bridging header after importing the framework and so I am not sure whether it is needed.

Rest of the process was pretty standard.