Framework
Framework可以理解为是一个封装了共享资源的具有层次结构的文件夹。同时它也是个Bundle,可以通过Bundle的相关API来访问。
制作Framework
Xcode Version: 12.0.1
点击Xcode -> File -> New -> Target
,选择iOS
滑动到底部,在Framework & Library
一栏下选择Framework
。
Mach-O Type设置
Mach-O Type分为以下五种类型,一般常见的有Dynamic Library
和Static Library
,这里我们设置为Static Library
。
参数名 | 含义 |
---|---|
Executable | 可执行二进制文件 |
Dynamic Library | 动态库 |
Bundle | 非独立二进制文件,显式加载 |
Static Library | 静态库 |
Relocatable Object File | 可重定位的目标文件,中间结果 |
静态库(Static Libraries)
静态库是我们使用最常见的方式,主要以.a、.lib、.framework、.xcframework形式存在,平时用到的三方SDK基本上都是这种,它有以下几个特点:
- 在项目编译过程中就需要导入到目标程序中,因此会一定程度上增加包的体积,当库有修改时,也必须重新编译和发布。
- App启动时就会载入内存,因此在使用时不需要外部加载,速度快,但相应的也增加了App启动时间。
- 减少耦合性,静态库中是不可以包含其他静态库的,使用的时候要另外导入它的依赖库,可以最大限度的保证了每一个静态库都是独立的,不会重复引用。
- 减少对外界的依赖,如果使用第三方动态库,库找不到的话程序会崩溃。
动态库(Dynamic Libraries)
动态库我们使用最多的就是UIkit.framework和Fundation.framework,动态库主要以.dylib、.so、.dll、.tbd、.framework、.xcframewor的形式存在,它有以下几个特点:
- App按需加载,加速App的启动。
- 不需要拷贝到可执行文件中,减少包的体积。
- 维护和更新比较方便,只要接口不变,依赖动态库的App就不用重新编译。
- 在制作上可以直接包含静态库,因此在使用时不需要再次导入其他的依赖库。
在 iOS 8 之前,iOS 平台不支持开发者使用用户自己的动态Framework,这种限制可能是出于安全的考虑。因为 iOS 应用都是运行在沙盒当中,不同的程序之间不能共享代码,同时动态下载代码又是被苹果明令禁止的,没办法发挥出动态库的优势,实际上动态库也就没有存在的必要了。
但是,iOS 8/Xcode 6 推出之后,因为Extension的出现,iOS添加了对动态库的支持,Extension 和 App 是两个分开的可执行文件,同时需要共享代码,这种情况下动态库的支持就是必不可少的了。
不过这种动态 Framework 和系统的 UIKit.Framework 还是有很大区别,系统的 Framework 不需要拷贝到目标程序中,我们自己做出来的 Framework 哪怕是动态的,最后也还是要拷贝到 App 中(App和Extension的 Bundle是共享的),因此苹果又把这种 Framework 称为 Embedded Framework。
在导入自己制作的动态库时,需要在Embedded Binaries中导入,不然会报image not found,程序崩溃。
支持Bitcode
Enable Bitcode:
YES
Deployment Postprocessing:
YES
Strip Style:
Debugging Symbols
Other C Flags:
-fembed-bitcode / fembed-bitcode
其他相关设置
Architectures:
指定工程将被编译成支持哪些指令集,一般不包含armv7s,如果需要可以额外添加。
Valid Architecures:
指定可能支持的指令集,该列表和Architectures列表的交集,将是Xcode最终生成的二进制包所支持的指令集。
Build Active Architecture Only:
编译时是否只保留对应设备的指令集。
Dead Code Stripping:
是否消除无效代码,设置为NO关闭对代码中dead,unreachable代码过滤。
Link With Standard Libraries:
是否用标准库链接,设置为 NO 避免重复链接。
编写代码
这里不再赘述,对于OC而言,需要暴露的头文件需要添加到 Build Phases -> Headers -> Public
里。
如果是Swift Framework,需要暴露的方法使用 public
和 @objc
修饰,对于那些需要继承重写的类使用 open
修饰。
创建通用包
编译过后,会在Products
生成一个包,如果是真机则是真机包,如果是模拟器则是模拟器包。这两个包的区别是包含的指令集不一致。
真机:armv7|armv7s|arm64
模拟器:i386|x86_64
在终端通过lipo指令查看包的指令集。
lipo -info xxxx/ProjectName.framework/ProjcetName
不同的包只能在固定的场景下使用,为了省去来回切换的麻烦,我们需要将两个包进行合并。
lipo -create PATH1 PATH2 -output PATH3
这一步是为了合并两者的二进制文件ProjectName.framework/ProjcetName
,PATH1、PATH2和PATH3均为二进制文件路径,合并完成之后将真机或者模拟器的二进制文件替换为合并之后的。
基本到这里就结束了,但是对于Swfit来讲,仅仅合并二进制文件是不够的,还需要合并ProjectName.framework/Modules/
文件夹和swift header file
,所以建议还是采用脚本的方式。
点击Xcode -> File -> New -> Target
,选择Other
,创建Aggregate
,在新建的Aggregate下点击+
号添加New Run Script Phase
,添加以下脚本:
1 | # Set bash script to exit immediately if any commands fail. |
打包上线时删除未使用的指令集:
1 | APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}" |
XCFramework
XCFramework 是 Xcode 11 引入的,一个可分发的二进制包,它包含了 framework 的一个或多个变体,XCFramework的好处就是用 Xcode 发布的时候,Xcode 会自动选用正确的指令集Framework,省去了手动移除动态库中的模拟器指令集的工作。
设置方式和Framework一致,可以是静态的也可以是动态的,打包XCFramework的脚本如下:
1 | #!/bin/sh -e |
常见错误
Category错误
错误描述:静态库中如果包含了Category(分类),有时候在使用静态库的工程中会报“方法找不到”的错误(unrecognized selector sent to instance)。
具体原因:参见编译参数-objc说明
解决方案:在使用静态库的工程中配置Other Linker Flags
为-ObjC
制作成动态库
错误描述:在使用静态库时,运行报错(Reason: Image Not Found)
具体原因:可能由于没有设置Mach-O Type
,做的是动态库,在使用的时候需要额外加一个步骤,要把Framework同时添加到General --> Embedded Binaries
中。
解决方案:将Mach-O Type
设置为Static Library
OC项目引用Swift Framework
错误日志:
1 | Undefined symbol: __swift_FORCE_LOAD_$_swiftCompatibilityDynamicReplacements |
解决方式:在项目的Build Setting -> LIBRARY_SEARCH_PATHS
添加
1 | $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) |
错误日志:
解决方式:修改项目配置Build Setting -> ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES
为YES
错误日志:
解决方式:在项目的Build Setting -> LD_RUNPATH_SEARCH_PATHS
添加(一定要加在第一行)
1 | /usr/lib/swift |
如果某些系统库找不到,则在项目的Build Phases -> Link Binary With Libraries
添加Swift依赖库:
1 | libswiftCompression.tbd |