Gigigo Workshop - Create an iOS Framework, document it and not die trying
1. Create an iOS
Framework, document it
and not die trying
by @alexruperez
2. • Fast iterative builds when developing the
framework. We may have a simple application that
has the .framework as a dependency and we
want to quickly iterate on development of
the .framework.
• Infrequent distribution builds of the .framework.
• Resource distribution should be intuitive and not
bloat the application.
• Setup for third-party developers using
the .framework should be easy.
4. • Developers expect to be able to import your framework by importing the
<YourFramework/YourFramework.h> header. Ensure that your project has such a
header (if you created a new static library then there should already be a
YourFramework.h and YourFramework.m file; you can delete the .m).
• Add Build Phases from the menu. Click on Editor > Add Build Phase -> Add Copy
Headers Build Phase. Note: If the menu options are grayed out, you'll need to click
on the whitespace below the Build Phases to regain focus and retry.
• You'll see 3 sections for Public, Private, and Project headers. To modify the scope of
any header, drag and drop the header files between the sections. Alternatively you
can open the Project Navigator and select the header. Next expand the Utilities
pane for the File Inspector (Cmd+Option+0).
• Look at the "Target Membership" group and ensure that the checkbox next to the .h
file is checked. Change the scope of the header from "Project" to "Public". You might
have to uncheck and check the box to get the dropdown list. This will ensure that
the header gets copied to the correct location in the copy headers phase.
5. • By default the static library project will copy private and
public headers to the same folder: /usr/local/include. To
avoid mistakenly copying private headers to our framework
we want to ensure that our public headers are copied to a
separate directory, e.g. Headers.
• To change this setting, select the project in the Project
Navigator and then click the "Build Settings" tab. Search for
"public headers" and then set the "Public Headers Folder
Path" to "Headers" for all configurations. If you are working
with multiple Frameworks make sure that this folder is
unique.
6. • We do not want to strip any code from the library;
we leave this up to the application that is linking
to the framework. To disable code stripping we
must modify the following configuration settings.
• "Dead Code Stripping" => No (for all settings)
• "Strip Debug Symbols During Copy" => No (for
all settings)
• "Strip Style" => Non-Global Symbols (for all
settings)
7. • Select Editor menu > Add Build Phase > Add
Run Script Build Phase
set -e!
!
mkdir -p "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/A/Headers"!
!
# Link the "Current" version to "A"!
/bin/ln -sfh A "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/Current"!
/bin/ln -sfh Versions/Current/Headers "${BUILT_PRODUCTS_DIR}/$
{PRODUCT_NAME}.framework/Headers"!
/bin/ln -sfh "Versions/Current/${PRODUCT_NAME}" "${BUILT_PRODUCTS_DIR}/$
{PRODUCT_NAME}.framework/${PRODUCT_NAME}"!
!
# The -a ensures that the headers maintain the source modification date so that we don't
constantly!
# cause propagating rebuilds of files that import these headers.!
/bin/cp -a "${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/" "$
{BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/A/Headers"
8.
9. • Add the static library target to the "Target
Dependencies”.
• Set “Arquitectures” and “Valid arquitectures” in
the Build Settings to i386, x86_64, armv7,
armv7s and arm64.
• Select Editor menu > Add Build Phase > Add
Run Script Build Phase
10. set -e!
set +u!
# Avoid recursively calling this script.!
if [[ $SF_MASTER_SCRIPT_RUNNING ]]!
then!
exit 0!
fi!
set -u!
export SF_MASTER_SCRIPT_RUNNING=1! !
SF_TARGET_NAME=${PROJECT_NAME}!
SF_EXECUTABLE_PATH="lib${SF_TARGET_NAME}.a"!
SF_WRAPPER_NAME="${SF_TARGET_NAME}.framework"!
SF_BUNDLE_NAME="${SF_TARGET_NAME}.bundle"! !#
The following conditionals come from!
# https://github.com/kstenerud/iOS-Universal-Framework! !
if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]!
then!
SF_SDK_PLATFORM=${BASH_REMATCH[1]}!
else!
echo "Could not find platform name from SDK_NAME: $SDK_NAME"!
exit 1!
fi! !
if [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]!
then!
SF_SDK_VERSION=${BASH_REMATCH[1]}!
else!
echo "Could not find sdk version from SDK_NAME: $SDK_NAME"!
exit 1!
fi! !
if [[ "$SF_SDK_PLATFORM" = "iphoneos" ]]!
then!
SF_OTHER_PLATFORM=iphonesimulator!
else!
SF_OTHER_PLATFORM=iphoneos!
fi! !
if [[ "$BUILT_PRODUCTS_DIR" =~ (.*)$SF_SDK_PLATFORM$ ]]!
then!
SF_OTHER_BUILT_PRODUCTS_DIR="${BASH_REMATCH[1]}${SF_OTHER_PLATFORM}"!
else!
echo "Could not find platform name from build products directory: $BUILT_PRODUCTS_DIR"!
exit 1!
fi!!#
Build the other platform.!
xcrun xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk ${SF_OTHER_PLATFORM}$
{SF_SDK_VERSION} BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" SYMROOT="${SYMROOT}" $ACTION! !#
Smash the two static libraries into one fat binary and store it in the .framework!
xcrun lipo -create "${BUILT_PRODUCTS_DIR}/${SF_EXECUTABLE_PATH}" "${SF_OTHER_BUILT_PRODUCTS_DIR}/${SF_EXECUTABLE_PATH}" -output "$
{BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/A/${SF_TARGET_NAME}"! !#
Copy the binary to the other architecture folder to have a complete framework in both.!
cp -a "${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/A/${SF_TARGET_NAME}" "${SF_OTHER_BUILT_PRODUCTS_DIR}/$
{SF_WRAPPER_NAME}/Versions/A/${SF_TARGET_NAME}"! !
rm -rf "${PROJECT_DIR}/Framework/"!
mkdir "${PROJECT_DIR}/Framework/"!
cp -rf "${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}" "${PROJECT_DIR}/Framework/"!
cp -rf "${BUILT_PRODUCTS_DIR}/${SF_BUNDLE_NAME}" "${PROJECT_DIR}/Framework/" 2>/dev/null || :
11. • Add the Framework Project to your Application
Project
• Select your project in the Project Navigator and
open the "Build Phases" tab. Expand the "Target
Dependencies" group and click the + button.
Select the static library target and click “Add".
• Expand the "Link Binary With Libraries" phase
and click the + button. Select the .a file that's
exposed by your framework's project and then
click add.