[5] AppStore Submission / Stripping redundant architectures from HDMMapCore.framework

Heidelberg Mobil provides HDMMapCore 2.x as a single dynamic framework, including everything that is needed to embed an indoor map into an iOS application.

"Single" means that HDMMapCore.framework includes binaries for all needed platforms: 

$ lipo -info HDMMapCore.framework/HDMMapCore 
Architectures in the fat file: HDMMapCore.framework/HDMMapCore are: i386 x86_64 armv7 arm64

This approach is convenient and straightforward. However, there’s one drawback - because dynamic framework is linked at runtime, when a dynamic library is compiled separately to the app it ends up in, it’s impossible to tell which architectures will actually be needed. Therefore, Xcode will just copy the whole thing into your application bundle at compile time. Other than the wasted disk space, there’s no real drawback to this. In practice, however, iTunes Connect doesn’t like us adding unused binary slices:

So, how do we work around this?

  • We could use static framework instead like we did for HDMMapCore 1.x when dynamic libraries were unavailable (before iOS 8). However, this does not work for Swift code, as Swift is not ABI stable. Furthermore, static framework is a step back and will not be supported.

  • We can provide two types of HDMMapView.framework: develop and appStore. Maintaining two SDK versions inconveniences both the SDK developers and end-users.

  • In order to try resolving this, try to strip the redundant platform at build-time.

The last option can be implemented by adding the following script to your build steps. Insert it after your step to embed frameworks and set it to use /bin/sh:

APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"

# This script loops through the frameworks embedded in the application and
# removes unused architectures.
find "$APP_PATH" -name 'HDMMapCore.framework' -type d | while read -r FRAMEWORK
do
    FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
    FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
    echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"

    EXTRACTED_ARCHS=()

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

    echo "Merging extracted architectures: ${ARCHS}"
    lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
    rm "${EXTRACTED_ARCHS[@]}"

    echo "Replacing original executable with thinned version"
    rm "$FRAMEWORK_EXECUTABLE_PATH"
    mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"

done


The script will look through your built application’s Frameworks folder and make sure only the architectures you’re building are present in each Framework.

Results:


Bonus:

Size of original HDMMapCore.Framework

$ du -sh HDMMapCore.framework
48M     HDMMapCore.framework


Size of Stipped HDMMapCore.Framework inside the App.

$ du -sh HDMMapCore.framework
5.9M    HDMMapCore.framework