静态库本质是编译后的代码库,引用静态库可以有效减少项目的编译速度。一般来说,我们会选择对代码无依赖,功能相对独立的模块编译成静态库,静态库的另一个功能是可以对第三方仅提供代码能力而不开源代码。
静态库制作
制作的方法有很多种,这里主要说两种。
1.0、静态库工程创建
通过创建static Library工程,File->New->Project->Static Library
这是一个纯静态库工程,只能build,无法run,也就是说无法跑起来看看实际的运行效果,不过只要代码规范写得好,每个接口都写好测试用例,也能保证功能的准确性,当然,最好还是要配套一个demo工程。
下图是文件结构,把你的代码文件放在里面即可,Products下是生成的.a静态库,红色表示没有当前架构对应的静态库,白色表示有。
1.1 静态库编译
静态库的编译,我们一般都是选择release模式进行编译,因为要提供给别的工程用,需要上架APP商城的。
那么如何选择Release模式呢?
-
首先下图的地方点击Edit Schema
- 选择Release,然后close即可
设置好Release模式后,即可进行编译,Command + B。编译成功后可以看到Products目录下的.a文件变成白色,这个也就是我们要的静态库了。
选中libstaticLib.a右键,show in finder,即可在finder里面打开,可以看到,这里有Debug的目录和Release的目录,分别对应的是debug模式构建出的静态库和Release模式构建出的静态库,iphoneos和iphonesimulator分别对应真机和模拟器编译产物
如何选择真机或模拟器编译?
这个比较简单,如下图所示,选择Any iOS Device或者选择你的iPhone,再build即可真机编译,同理,随便选个模拟器就是模拟器编译了。
真机和模拟器的编译产物有什么不一样呢?
其实就是架构的不一样,我们可以通过下面的命令来查看静态库支持的架构
lipo -info xxx.a
真机构建出的静态库,可以看出是支持arm64和armv7的
Architectures in the fat file: /Users/userName/Library/Developer/Xcode/DerivedData/staticLib-cmbwfxsgznzsihdozkkclljjvpza/Build/Products/Release-iphoneos/libstaticLib.a are: arm64 armv7
模拟器的通过命令可以查看支持arm64 x86_64 i386
Architectures in the fat file: /Users/welling/Library/Developer/Xcode/DerivedData/staticLib-cmbwfxsgznzsihdozkkclljjvpza/Build/Products/Release-iphonesimulator/libstaticLib.a are: arm64 x86_64 i386
可以看出真机和模拟器编译出的静态库支持的架构是不一样的,
那么架构那么多,如何选择呢?
不同的手机使用的架构不一样,不同架构对应的可执行代码也不一样,不同的可执行代码肯定得经过不一样的编译。那么要如何编译不同的架构的静态库呢?
按照现在市场的机器,一般我们支持4个架构就可以了 armv7 arm64 x86_64 i386
如下图所示,Build Settings的Architectures表示这次构建支持的架构,一般默认情况下,真机编译会支持arm64和armv7,模拟器编译会支持arm64 x86_64 i386三种。
有的时候模拟器会缺少x86_64(我遇到过一次,构建出的静态库没有x86_64),这个时候需在Architectures里面按下图方法添加需要的架构再编译,如果还不行需要看看VALID_ARCHS里面是否缺少了x86_64,缺少就加上即可。
有的时候甚至找不到VALID_ARCHS(真的有各种奇奇怪怪的情况),这个时候需要点击下图的加号,然后Add User-Defined Setting,自己创建一个VALID_ARCHS
另外还需要注意的是另一个设置项Build Active Architecture Only,这个的Release一定要设置为NO,这个设置项的意思是,构建的时候是否只构建当前激活的架构,比如如果你用模拟器iPhone12 来构建,那么它只会生成模拟器iPhone12的架构,也就是x86_64,其他的arm64等不会生成。
- 静态库也有支持的最低系统版本,所以需要在下图的位置指定
- 头文件的指定
1.2、合成静态库
一个静态库给到别人,至少得支持上面说到的四个架构,但是,我们分别通过模拟器和真机来编译,生成的是两个静态库,怎么把它们合成一个静态库呢?还是通过lipo工具,使用下面的命令即可
lipo -create iPhoneos/staticlib.a simulator/staticlib.a -output staticlib.a
iPhoneos/staticlib.a 表示真机编译出的静态库路径
simulator/staticlib.a 表示模拟器编译出的静态库路径
当然两个的位置可以互换
最后的 staticlib.a 表示最终输出的静态库路径,也就是合体之后的
但是,有时候会遇到这样的错误
xxx have the same architectures (arm64) and can't be in the same fat output file
这个意思是说,两个静态库中后相同的架构arm64,不能把它们都合在一起。这时候需要把模拟器中的arm64移除。(为什么是移除模拟器的而不是真机的?这里又有坑,遇到过项目接入静态库后真机编译无法使用模拟器的arm64导致编译失败)
如何移除呢?
依然是使用lipo
lipo path/xxxx.a -remove arm64 -output newxxx.a
到此为止,已经生成来一个可以给别人用的静态库了
1.3、分类
如果静态库中含有系统类的分类,或者其他第三方库的分类(即只有分类,主类不在静态库里),这个时候被其他项目接入了,很有可能就会导致unrecognized selector sent to instance
的crash。这是因为分类的主类不在,导致分类的方法并没有其他加载,也就是方法的地址没有被记录,导致在运行的时候找不到该方法。
解决方案
在接入静态库的工程文件->Build Settings->Other Linker Flags 做以下其中一项修改即可
-
-force_load 静态库路径
-
-ObjC
-
-all_load
如果你的静态库是上pod库的,可以在podspec文件里面加下面一句代码
s.user_target_xcconfig = { 'OTHER_LDFLAGS' => '-force_load ${PODS_ROOT}/你的静态库/静态库.a' }
如果你的静态库依赖其他第三方库如何构建?
-
方法1: 不和第三方库一起构建,即第三方库不会编译进我的静态库里面,只需在search path的时候把依赖的头文件引用一下,即可顺利构建成功。
方法2: 和第三方库一起构建,这个更简单,直接把第三方库拖入你的静态库项目中即可,但是通过这种方法构建出的静态库会有很多问题,比如一个项目中引入了第三方库A和你的静态库,而你的静态库也包含了第三方静态库A,则会发生冲突。建议使用方法1来处理。
2.0、通过target来生成framework
这种方式生成的其实是披着动态库外衣的静态库
2.1 创建target
按照下面图的步骤来,点击➕,找到framework,点击Next,起个名字,点击Finish即可。
2.2 转成静态库
设置target的machO type为静态库类型
在创建的target目录下添加你的静态库代码,然后我们会发现,这个target其实和我们上面说的创建静态库的方法是一样的,各种设置项都一样的,编译方法也是一样。
差别在哪呢?
a、 生成产物不一样,build后,会在Products目录下生成一个.framework文件。
当我们在finder中打开来看,发现里面和.a静态库是一样
b、方便调试。通过target的方式,你可以直接在demo工程中源码使用,debug十分方便。
c、打包方式不一样。我们把.app文件解压出来看
当然,这是因为target在这个demo工程中是源码接入的,如果把这个framework给其他工程接入,也会和.a一样集成到二进制文件里面。