Creating a Universal Framework in Xcode 9

A while back, I posted a script for creating a universal iOS framework (i.e. one that will run in both the simulator as well as on an actual device) in Xcode 7. The following is an updated version for use with Xcode 9. This version should work with both Swift and Objective-C projects:

FRAMEWORK=

BUILD=build
FRAMEWORK_PATH=$FRAMEWORK.framework

# iOS
rm -Rf $FRAMEWORK-iOS/$BUILD
rm -f $FRAMEWORK-iOS.framework.tar.gz

xcodebuild archive -project $FRAMEWORK-iOS/$FRAMEWORK-iOS.xcodeproj -scheme $FRAMEWORK -sdk iphoneos SYMROOT=$BUILD
xcodebuild build -project $FRAMEWORK-iOS/$FRAMEWORK-iOS.xcodeproj -target $FRAMEWORK -sdk iphonesimulator SYMROOT=$BUILD

cp -RL $FRAMEWORK-iOS/$BUILD/Release-iphoneos $FRAMEWORK-iOS/$BUILD/Release-universal
cp -RL $FRAMEWORK-iOS/$BUILD/Release-iphonesimulator/$FRAMEWORK_PATH/Modules/$FRAMEWORK.swiftmodule/* $FRAMEWORK-iOS/$BUILD/Release-universal/$FRAMEWORK_PATH/Modules/$FRAMEWORK.swiftmodule

lipo -create $FRAMEWORK-iOS/$BUILD/Release-iphoneos/$FRAMEWORK_PATH/$FRAMEWORK $FRAMEWORK-iOS/$BUILD/Release-iphonesimulator/$FRAMEWORK_PATH/$FRAMEWORK -output $FRAMEWORK-iOS/$BUILD/Release-universal/$FRAMEWORK_PATH/$FRAMEWORK

tar -czv -C $FRAMEWORK-iOS/$BUILD/Release-universal -f $FRAMEWORK-iOS.tar.gz $FRAMEWORK_PATH $FRAMEWORK_PATH.dSYM

When located in the same directory as the .xcodeproj file, this script will invoke xcodebuild twice on a framework project and join the resulting binaries together into a single universal binary. It will then package the framework up in a gzipped tarball and place it in the same directory.

However, apps that contain “fat” binaries like this don’t pass app store validation. Before submitting an app containing a universal framework, the binaries need to be trimmed so that they include only iOS-native code. The following script can be used to do this:

FRAMEWORK=$1
echo "Trimming $FRAMEWORK..."

FRAMEWORK_EXECUTABLE_PATH="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/$FRAMEWORK.framework/$FRAMEWORK"

EXTRACTED_ARCHS=()

for ARCH in $ARCHS
do
    echo "Extracting $ARCH..."
    lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
    EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
done

echo "Merging binaries..."
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
rm "${EXTRACTED_ARCHS[@]}"

rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"

echo "Done."

To use this script:

  1. Place the script in your project root directory and name it trim.sh or something similar
  2. Create a new “Run Script” build phase after the “Embed Frameworks” phase
  3. Rename the new build phase to “Trim Framework Executables” or similar (optional)
  4. Invoke the script for each framework you want to trim (e.g. ${SRCROOT}/trim.sh)

For more ways to simplify iOS app development, please see my projects on GitHub:

  • MarkupKit – Declarative UI for iOS and tvOS
  • Kilo – Lightweight REST for iOS and tvOS

11 thoughts on “Creating a Universal Framework in Xcode 9

  1. Hey I tried a similar script and the problem I’m getting is that the universal framework is unusable in my test app, it just says No Module found name “…”, Any ideas whats going on? If I revert back to a simulator/device only framework app works fine.

    Like

  2. Thanks for the quick reply, well by similar I mean I didn’t copy and paste your solution but im using the exact same lipo command, and I do a lipo -info to doublecheck what architectures were included and it tells me all the arch’s are there arm64, armv7, armv7s, x86_64 and i386. When I’m implementing the framework, it doesn’t seem to matter whether I run it on a device or simulator, I get the same “no module found” error. Although I will say I recently “fixed” the no module found error by including $(PROJECT_DIR) in the search paths settings of the target/project. But now it complains that it cannot find any of the classes that are included in my module.

    Also one last point, do you have a reference to the Apple guidelines where they say “fat” binaries will not pass App Store validation. I’m not doubting you, it makes sense now that I think about it but I just needed something from Apple that says it directly.

    Like

    1. How are you using the generated framework? You should be able to drop it into the Embedded Binaries section of your project settings.

      I’m not sure the App Store thing is officially documented anywhere, but if you try to submit an app without trimming the framework, it will be rejected. Do a quick search on “app store universal binary” and you’ll find a number of references to it.

      Like

  3. Wow thanks again for the reply, and your project looks great. I used it and I was able to import it perfectly fine. So I tried running your script as-is and it wouldn’t run because of the paths being appended with iOS, made some minor adjustments and it while it produced the binary, the fat framework was missing the armv7s architecture. I added the main xcodebuild command as well to be:

    xcodebuild build -project $FRAMEWORK/$FRAMEWORK.xcodeproj -arch armv7s -arch armv7 -arch arm64 -configuration Release -target $FRAMEWORK -sdk iphoneos SYMROOT=$BUILD
    xcodebuild build -project $FRAMEWORK/$FRAMEWORK.xcodeproj -target $FRAMEWORK -sdk iphonesimulator SYMROOT=$BUILD

    And Yes btw this is all in swift 4. I was still getting the same error “No such module found” so as another test, I tried matching up the build settings of my project to yours. It ended up causing some other build issues. I reverted all the changes from my project and reran the same build script and voila, it worked. I’m still confused why it didn’t work the first time, as I fundamentally didn’t change much but I’m not gonna question Apple lol … Any way a big thanks friend, I really appreciate the guidance.

    Like

Comments are closed.