framework--多架构(Multi-Architecture)编译

续上一篇 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。


找到Add Target按钮
找到iOS/Other/Aggregate
结果

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指令,你可以看到这些片段的一个分组。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容