建立具有依赖关系的封闭源Swift框架的食谱

除了ABI兼容性问题外,有时您可能希望分发Swift框架的二进制版本。 该框架应为“胖”框架(同时支持模拟器和iOS设备架构),并且可能依赖于其他第三方框架。 没有为此的Xcode项目模板,但是您会在Internet上找到有用的信息。 基本上可以归结为使用xcodebuild创建框架的两个版本,一个用于模拟器,一个用于真实的iOS设备,然后使用lipo命令将它们组合为一个胖框架。

这似乎很简单,但是在网上找到以下说明(此处,此处和此处的示例)之后,当在应用程序中使用框架的二进制版本时,我们遇到了运行时崩溃。 当我们的框架在以下示例中称为MyFramework尝试使用来自任何第三方框架依赖项的符号时,将发生崩溃。 当MyFramework尝试使用RxSwiftObservableType时,崩溃看起来像这样:

  dyld:懒惰的符号绑定失败:找不到符号:__TIFE7RxSwiftPS_14ObservableType9subscribeFT6onNextGSqFwx1ET__7onErrorGSqFPs5Error_T__11onCompletedGSqFT_T__10onDisposedGSqFT_T___PS_10Disposable_A0_ 
 引用自:/Users/aaron/Library/Developer/CoreSimulator/Devices/335F6B8A-0CBC-406D-9559–5718D4910F5A/data/Containers/Bundle/Application/47AA11E3-C2E5–4FCB-A86C-36092990EF0E/Consumer.app/Frameworks MyFramework.framework / MyFramework 
 预期位于:/Users/aaron/Library/Developer/CoreSimulator/Devices/335F6B8A-0CBC-406D-9559–5718D4910F5A/data/Containers/Bundle/Application/47AA11E3-C2E5–4FCB-A86C-36092990EF0E/Consumer.app/Frameworks RxSwift.framework / RxSwift 

经过大量的调查和试验,我们发现了一个过程,该过程可以生成胖框架,当引用其他第三方框架的符号时,该框架不会崩溃。 这是我无法完全解释问题或原因的原因之一,但我想在这里分享:

创建你的框架

使用Cocoa Touch Framework Xcode模板,随意选择其他框架(Cocoapods,Carthage,拖放)等。

创建目标以构建胖框架

在“跨平台”部分中使用“聚合”目标类型。 这创建了一个最小目标,我们可以向其中添加一个运行脚本构建阶段。 我将这个目标BuildFatFramework

将胖框架构建外壳脚本添加到目标

将运行脚本构建阶段添加到BuildFatFramework目标,然后向其中添加以下shell脚本:

  #1 
#设置bash脚本在任何命令失败时立即退出。
 设置-e 
  #2 
#设置一些常量供以后使用。
  FRAMEWORK_NAME =“ MyFramework” 
OUTPUT_DIR =“ $ {SRCROOT} / build”
  #3 
#如果存在先前版本的残余,请将其删除。
 如果[-d“ $ {OUTPUT_DIR}”]; 然后 
rm -rf“ $ {OUTPUT_DIR}”
科幻
  #4 
#构建设备和模拟器的框架(使用
#所有所需的架构)。
  xcodebuild -workspace“ $ {FRAMEWORK_NAME} .xcworkspace”-方案“ $ {FRAMEWORK_NAME}”-配置版本-arch arm64 -arch armv7 -arch armv7s only_active_arch = no define_module = yes -sdk“ iphoneos” -derivedDataPath“ $ {OUTPUT_DIR}” 
  xcodebuild -workspace“ $ {FRAMEWORK_NAME} .xcworkspace”-方案“ $ {FRAMEWORK_NAME}”-配置版本-arch x86_64 -arch i386 only_active_arch = no define_module = yes -sdk“ iphonesimulator” -derivedDataPath“ $ {OUTPUT_DIR}” 
  #5 
#删除.framework文件(如果以前运行的文件存在)。
 如果[-d“ $ {OUTPUT_DIR} / $ {FRAMEWORK_NAME} .framework”]; 然后 
rm -rf“ $ {OUTPUT_DIR} / $ {FRAMEWORK_NAME} .framework”
科幻
  #6 
#复制框架的设备版本。
  cp -r“ $ {OUTPUT_DIR} / Build / Products / Release-iphoneos / $ {FRAMEWORK_NAME} .framework”“ $ {OUTPUT_DIR} / $ {FRAMEWORK_NAME} .framework” 
  #7 
#将框架内的框架可执行文件替换为
#通过合并设备和模拟器创建的新版本
#带有lipo的框架可执行文件。
  lipo -create -output“ $ {OUTPUT_DIR} / $ {FRAMEWORK_NAME} .framework / $ {FRAMEWORK_NAME}”“ $ {OUTPUT_DIR} / Build / Products / Release-iphoneos / $ {FRAMEWORK_NAME} .framework / $ {FRAMEWORK_NAME}” $ {OUTPUT_DIR} / Build / Products / Release-iphonesimulator / $ {FRAMEWORK_NAME} .framework / $ {FRAMEWORK_NAME}” 
  #8 
#将模拟器的Swift模块映射复制到
#框架。 从步骤6开始,设备映射已经存在。
  cp -r“ $ {OUTPUT_DIR} / Build / Products / Release-iphonesimulator / $ {FRAMEWORK_NAME} .framework / Modules / $ {FRAMEWORK_NAME} .swiftmodule /”“ $ {OUTPUT_DIR} / $ {FRAMEWORK_NAME} .framework / Modules / $$ {FRAMEWORK_NAME} .swiftmodule” 

根据需要自定义此脚本以适合您的项目。

从BuildFatFramework目标启动构建

现在,您可以通过运行Xcode项目的BuildFatFramework目标来构建胖框架。

非常重要:如果从命令行进行build ,请确保使用xcodebuild ,将BuildFatFramework指定为方案,将build指定为操作(如果未指定操作,则默认为archive )。 这是导致我们无碰撞框架的关键步骤

archive操作是不正确的,而且,当您构建BuildFatFramework方案时,似乎Xcode正在做一些事情来正确设置构建环境。 将构建胖框架的shell脚本的内容保存到独立文件中,然后绕过BuildFatFramework方案直接运行它,导致框架崩溃。

这是我们在CI系统中用来启动fat框架构建的命令:

  xcodebuild-方案BuildFatFramework -workspace MyFramework.xcworkspace构建 

希望Xcode将来会为构建胖框架提供更好的支持,但是在那之前,我希望这些信息对那里的人有用。