续上一篇 Framework--结构篇
下一篇 framework--怎么样使用bundle来共享资源
1、需求
1)iOS app需要在许多不同的CPU架构下运行:
arm7: 在最老的支持iOS7的设备上使用
arm7s: 在iPhone5和5C上使用
arm64: 运行于iPhone5S的64位 ARM 处理器 上
i386: 32位模拟器上使用
x86_64: 64为模拟器上使用
2)每个CPU架构都需要不同的二进制数据,当你编译一个应用时,无论你目前正在使用哪种架构,Xcode都会正确地依照对应的架构编译。例如,如果你想跑在虚拟机上,Xcode只会编译i386版本(或者是64位机的x86_64版本)。
3)这意味着编译会尽可能快地进行,当你归档一款app或者构建app的发布版本(release mode)时,Xcode会构建上述三个用于真机的ARM架构。因此这样app就可以跑在所有设备上了。不过,其他的编译架构又如何呢?
4)当你创建你的framework时,你自然会想让所有开发者都能在所有可能的架构上运行它。因此你需要让Xcode在所有架构下都进行编译。这一过程实际上是创建了二进制FAT(File Allocation Table,文件配置表),它包含了所有架构的片段(slice)。
5)**注意: **这里实际上强调了创建依赖静态库的示例项目的另一个原因:库仅仅在示例项目运行所需要的架构下编译,只有当有变化的时候才重新编译
2、操作
1)使用在A工程中的一个新的目标来构建framework,在项目导航栏中选择A,然后点击已经存在的目标下面的Add Target按钮->找到iOS/Other/Aggregate->点击Next->将目标命名为Framework。
2)注意:
为什么使用集合(Aggregate)目标来创建一个framework呢?
因为OS X对库的支持更好一些,事实上,Xcode直接为每一个OS X工程提供一个Cocoa Framework编译目标。基于此,你将使用集合编译目标,作为Bash脚本的连接串来创建神奇的framework目录结构。
3)为了确保每当这个新的framework目标被创建时,静态链接库都会被编译,你需要往静态库目标中添加依赖(Dependency)。在库工程中选择Framework目标,在Build Phases中添加一个依赖。展开Target Dependencies面板,点击 + 按钮选择A静态库
4)这个目标的主要编译部分是多平台编译,使用一个脚本来做到这一点。和之前做的一样,在Framework目标下,选择Build Phases栏,点击Editor/Add Build Phase/Add Run Script Build Phase,创建一个新的Run Script Build Phase。
5)双击Run Script,重命名脚本的名字。这次命名为MultiPlatform Build。
6)在脚本文本框中粘贴下面的Bash脚本代码:
set -e
# If we're already inside this script then die
if [ -n "$RW_MULTIPLATFORM_BUILD_IN_PROGRESS" ]; then
exit 0
fi
export RW_MULTIPLATFORM_BUILD_IN_PROGRESS=1
RW_FRAMEWORK_NAME=${PROJECT_NAME}
RW_INPUT_STATIC_LIB="lib${PROJECT_NAME}.a"
RW_FRAMEWORK_LOCATION="${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework"
1、set –e确保脚本的任何地方执行失败,则整个脚本都执行失败。这样可以避免让你创建一个部分有效的framework。
2、接着,用RW_MULTIPLATFORM_BUILD_IN_PROGRESS变量决定是否循环调用脚本,如果有该变量,则退出。
3、然后设定一些变量。该framework的名字与项目的名字一样。也就是A,另外,静态lib的名字是libA.a。
7)接下来,用脚本设置一些函数,这些函数一会项目就会用到。
把下面的代码加到脚本的底部:
function build_static_library {
# Will rebuild the static library as specified
# build_static_library sdk
xcrun xcodebuild -project "${PROJECT_FILE_PATH}" \
-target "${TARGET_NAME}" \
-configuration "${CONFIGURATION}" \
-sdk "${1}" \
ONLY_ACTIVE_ARCH=NO \
BUILD_DIR="${BUILD_DIR}" \
OBJROOT="${OBJROOT}" \
BUILD_ROOT="${BUILD_ROOT}" \
SYMROOT="${SYMROOT}" $ACTION
}
function make_fat_library {
# Will smash 2 static libs together
# make_fat_library in1 in2 out
xcrun lipo -create "${1}" "${2}" -output "${3}"
}
1、build_static_library把SDK作为参数,例如iPhone7.0,然后创建静态lib,大多数参数直接传到当前的构建工作中来,不同的是设置ONLY_ACTIVE_ARCH来确保为当前SDK构建所有的结构。
2、make_fat_library使用lipo将两个静态库合并为一个,其参数为两个静态库和结果的输出位置。从这里了解更多关于lipo的知识。
为了使用这两个方法,接下来脚本将定义更多你要用到的变量,你需要知道其他SDK是什么,例如,iphoneos7.0应该对应iphonesimulator7.0,反过来也一样。你也需要找到该SDK对应的编译目录。
把下面的代码添加到脚本的底部:
# 1 - Extract the platform (iphoneos/iphonesimulator) from the SDK name
if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]; then
RW_SDK_PLATFORM=${BASH_REMATCH[1]}
else
echo "Could not find platform name from SDK_NAME: $SDK_NAME"
exit 1
fi
# 2 - Extract the version from the SDK
if [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]; then
RW_SDK_VERSION=${BASH_REMATCH[1]}
else
echo "Could not find sdk version from SDK_NAME: $SDK_NAME"
exit 1
fi
# 3 - Determine the other platform
if [ "$RW_SDK_PLATFORM" == "iphoneos" ]; then
RW_OTHER_PLATFORM=iphonesimulator
else
RW_OTHER_PLATFORM=iphoneos
fi
# 4 - Find the build directory
if [[ "$BUILT_PRODUCTS_DIR" =~ (.*)$RW_SDK_PLATFORM$ ]]; then
RW_OTHER_BUILT_PRODUCTS_DIR="${BASH_REMATCH[1]}${RW_OTHER_PLATFORM}"
else
echo "Could not find other platform build directory."
exit 1
fi
上面四句声明都非常相似,都是使用字符串比较和正则表达式来确定RW_OTHER_PLATFORM和RW_OTHER_BUILT_PRODUCTS_DIR。
详细解释一下上面四句声明:
a、SDK_NAME将指代iphoneos7.0和iphonesimulator6.1,这个正则表达式取出字符串开头不包含数字的那些字符,因此,其结果是iphoneos 或 iphonesimulator。
b、这个正则表达式取出SDK_NAME中表示版本用的数字,7.0或6.1等。
c、这里用简单的字符串比较来将iphonesimulator 转换为iphoneos,反过来也一样。
d、从构建好的工程的目录路径的末尾找出平台名称,将其替换为其他平台。这样可以确保为其他平台构建的目录可以被找到。这是将两个静态库合并的关键部分。
现在你可以启动脚本为其他平台编译,然后得到合并两静态库的结果。
在脚本最后添加下面的代码:
# Build the other platform.
build_static_library "${RW_OTHER_PLATFORM}${RW_SDK_VERSION}"
# If we're currently building for iphonesimulator, then need to rebuild
# to ensure that we get both i386 and x86_64
if [ "$RW_SDK_PLATFORM" == "iphonesimulator" ]; then
build_static_library "${SDK_NAME}"
fi
# Join the 2 static libs into 1 and push into the .framework
make_fat_library "${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \
"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \
"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"
a、首先,调用你之前定义好的函数为其他平台编译
b、如果你现在正在为模拟器编译,那么Xcode会默认只在该系统对应的结构下编译,例如i386 或 x86_64。为了在这两个结构下都进行编译,这里调用了build_static_library,基于iphonesimulator SDK重新编译,确保这两个结构都进行了编译。
c、最后调用make_fat_library将在当前编译目录下的静态lib同在其他目录下地lib合并,依次实现支持多结构的FAT静态库。这个被放到了framework中。
脚本的最后是简单的拷贝命令,将下面代码添加到脚本最后:
# Ensure that the framework is present in both platform's build directories
cp -a "${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}" \
"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}"
# Copy the framework to the user's desktop
ditto "${RW_FRAMEWORK_LOCATION}" "${HOME}/Desktop/${RW_FRAMEWORK_NAME}.framework"
a、第一条命令确保framework在所有平台的编译目录中都存在。
b、第二条将完成的framework拷贝到用户的桌面上,这一步是可选的,但我发现这样做可以很方便的存取framework。
3、运行结果
3.1、运行
选择Framework集合方案(aggregate scheme),按下cmd+B编译该framework。
3.2、预期
这一步将构建并在你的桌面上存放一个A.framework。
3.3、检查
为了检查一下我们的多平台编译真的成功了,启动终端,导航到桌面上的framework,然后执行下面语句。
$ cd ~/Desktop/A.framework
$ A.framework xcrun lipo -info A
第一条指令导航到framework中。
第二行使用lipo指令从A静态库中得到需要的信息,这将列出存在于该库中的所有片段。
检查的预期
一共有五种片段:i386, x86_64, arm7, arm7s 和 arm64,正如在编译时设定的那样。如果之前使用lipo –info指令,你可以看到这些片段的一个分组。