静态库
静态库的介绍
- 什么是库?
- 库是程序代码的集合,是共享程序代码的一种方式
- 根据源代码的公开情况,库可以分为2种类型
- 开源库
- 公开源代码,能看到具体实现
- 比如SDWebImage、AFNetworking
- 闭源库
- 不公开源代码,是经过编译后的二进制文件,看不到具体实现
- 主要分为:静态库、动态库
- 开源库
- 静态库和动态库
- 静态库和动态库的存在形式
- 静态库:.a 和 .framework
- 动态库:.dylib 和 .framework
- 静态库和动态库在使用上的区别
- 程序的运行
- 程序->编译->链接->运行
- 链接: 把所有的二进制文件组合在一起形成可执行性文件
- 静态库:
- 链接时,静态库会被完整地复制到可执行文件中,被多次使用就有多份冗余拷贝
- 动态库:
- 链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存
- 需要注意的是:项目中如果使用了自制的动态库,不能被上传到AppStore
-
静态库与动态库加载如下图:
- 程序的运行
- 静态库和动态库的存在形式
.a静态库的制作
直接制作.a静态库
- 创建项目时,选择Framework&Library–>Static Library
- 编写核心代码
- 编译静态库前,将头文件暴露出去
- TARGETS -> build Phases->copy Files
- 注意了,这一步其实没必要(很老的Xcode这里需要),因为Xcode会默认将这个项目的所有.文件暴露在这里了。
- 编辑真机静态库
- 选择当前的设备为通用真机设备:Generic iOS Device
- command +B 编译一下即可
- 在改项目的products目录找到
lib静态库名.a
,然后右击show in finder 找到这个.a 文件,复制出来即可- 此时会发现还有一个include文件,里面包含的就是这个.a对应的头文件.h
- 这个就是真机设备的.a静态库
- 编译模拟器静态库
- 选择任一款模拟设备
- 其他同真机静态库一样,获取到模拟器静态库.a
- 静态库整理:
- 新建一个文件夹,命静态库的名字
- 将include的.h文件都复制到这个文件中
- 将.a文件也复制到这里
- 创建一个项目,测试使用静态库即可.
- 问题抛出:到这里,我们是制作了两个静态库,一个是真机环境下做的额,另一个是模拟器环境下做的,那么我们使用时会发现,真机设备下只能用真机.a ,模拟器下的只能用模拟器.a,否则就会报错,怎么办呢?
静态库的架构问题
- 设备的CPU架构
- 不同的设备使用的CPU是不同的
- CPU的不同造成使用的CPU架构(指令集)不同
- 静态库有其支持的CPU架构
- 如果静态库在不支持的CPU架构上运行就会报错
- 模拟器使用的CPU架构
- iPhone4s-iPhone5 : i386
- iPhone5s-iPhone6plus : x86_64
- 真机使用的CPU架构
- iPhone3gs-iPhone4s : armv7
- iPhone5-iPhone5c : armv7s
- iPhone5s-最新 : arm64
- 唯一特殊:只要支持armv7的静态库可以在armv7s上正常运行
- 如何查看一个静态库到底支持哪些架构呢?
- 打开终端,cd 静态库文件目录
lipo -info 静态库
- 合并静态库
- 如果我们想要制作一个静态库,同时支持模拟器也支持真机,该怎么办呢?
- 那就需要将模拟器静态库跟真机静态库合并成一个静态库
lipo -create 静态库1 静态库2 -output 合并的静态库
-
如下:
lipo -create /Users/mac/Desktop/合并静态库/MJExtension/libMJExtension.a /Users/mac/Desktop/合并静态库/MJExtension2/libMJExtension.a -output /Users/mac/Desktop/合并静态库/合并后的静态库/MJExtension
-
合成后文件路径如下:
- release 和debug静态库的区别
- 从上面可操作可以看出,我们是在debug环境下生成的静态库。如果要想打release的静态库,可以先把Xcode的环境设置为release(至于怎么设置,百度),然后在生成静态库
- 静态库有4种形式
- 真机Debug版本
- 真机Release版本
- 模拟器Debug版本
- 模拟器Release版
- release版本的代码更加简洁,因此最好打包release版本。
- 注意:
- 如果静态库中包含了分类Category,默认是不会把分类的.m文件链接进来的,在使用静态库的工程中会报”方法找不到“的错误(unrecognized selector sent to instance)
- 解决办法:targets ->build settings ->other->other linker Flags->双击后边添加-ObjC即可
- 静态库是不能把资源文件bundle打进去的,制作静态库时,不能把.bundle拖进去,制作完成后,把.a/.h和bundle放到一个新的文件夹即可。
- 从上面我们知道,编译静态库时,每次编译只会生成一种架构
- 但是由于模拟器iPhone5 跟iPhone5s的CPU架构不同,一个是i386,一个是x86_64,那怎么办呢? 难道模拟器也要编译2个静态库,在合并吗? 这个是不需要的
- 在编译静态库之前,target-> build settings -> 搜索build -> build Active architetcture Only -> Debug ->设置为NO
- 而且在设置之前我们可以看到,这个地方release默认为NO ,而Debug默认为YES
- 如果静态库中包含了分类Category,默认是不会把分类的.m文件链接进来的,在使用静态库的工程中会报”方法找不到“的错误(unrecognized selector sent to instance)
在项目中直接制作静态库
- 优点:边写项目,边可以调试静态库。
- 实际开发中不是先在项目中写好文件,然后拖出来,制作静态库的,而是在项目中直接制作
- 在项目中,按照下面步骤即可
- 点击项目–>左下角+号
- 选择希望开发的静态库类型(.a/.framework)
- 创建静态库后,项目会多出以下内容:(比如静态库的名字为ZHExtension)
- 项目target会多出一个ZHExtension,并且图标为一个小房子
- 项目中会多出一个ZHExtension文件夹,而且里面默认有ZHExtension.h/.m文件
- 文件Products目录下回多出一个libZHExtension.a红色,文件,红色说明还没有编译生成静态库
- 如下图
- 编写将要打包的代码
- 所有你需要打成静态库的代码,都写在ZHExtension那个文件中
- 写好代码后,你在项目中运行,会发现报错
- 那是因为,你写的ZHExtension文件中的代码,是一个静态库中,而当前这个项目并没有引用到这个静态库,需要我们手动把这个静态库加载到当前项目。
- 编译写好的静态库
- 将项目中的target选择为静态库ZHExtension,然后command +B编译即可。
- 至于生成通用架构的静态库,方法跟之前一样。
- 点击项目–>左下角+号
.framework静态库的制作
直接制作.framework
- 创建项目时,选择Framework&Library–>Framework
- 默认创建的项目中有一个主头文件,可以将其他头文件在该头文件中import一下
- 编写核心代码
- 刚建好这个静态库后,仅仅只有一个主头文件
- 需要自己添加需要打包的.m/.h文件
- 编译库前,将头文件暴露出去
- Targets-> Build Phases ->Headers
- 会发现这里有3个类型
- public: 这个framework需要暴露的.h文件,默认只有主头文件
- Private:private,私有头文件,就是不需要暴露的
- project:这个库中所有的.h文件
- 如何将project中的.h添加到public中呢? 直接拖拽即可。
- 默认情况直接编译的库为动态库,可以修改配置改成静态库
- Framework默认为动态库,需要手动设置为静态库
- Framework默认为动态库,需要手动设置为静态库
- 选择模拟器和真机分别编译
- 打开Products,然后show in finder,即可看到.framework库
- 合并真机模拟器通用静态库
- 将真机.framework中的二进制文件、模拟器.framework中的二进制文件合并成一个通用的二进制文件(合并方法,跟.a一样)
- 然后将这个通用的二进制文件,替换掉真机或者模拟器.framework中的二进制文件即可。
在项目中直接制作.framework静态库
- 开始创建步骤和.a一样,只是选择framework方式
-
创建后项目中新增的文件如下图:(ZHRefresh)
- 注意,默认ZHRefresh文件中只有ZHRefresh.h跟一个info.plist文件,没有.m文件。
- 这个.h文件主要是用于其他.h文件导入到这个文件中的
- 编写将要打包的代码
- 所有你需要打成静态库的代码,都写在ZHRefresh那个文件夹中
- 而且将所有的.h文件都导入ZHRefresh.h
- 注意,framework,不需要手动导入项目库,Xcode会自动导入
- 设置需要暴露的文件
- 在Targets 中选择,ZHRefresh,然后设置暴露头文件。
- 在Targets 中选择,ZHRefresh,然后设置暴露头文件。
- 设置当前的frame为静态库
- 编译写好的静态库
- 将项目中的target选择为静态库ZHRefresh,然后command +B编译即可。
- 至于生成通用架构的静态库,方法跟之前一样。
- 注意:
- .framework使用时,import导入必须用<>导入
静态库总结
- .framework 为什么即时动态库又是静态库?
- 系统的.framework是动态库
- 我们自己建立的事静态库(默认是动态库,但是需要改成静态库)
- 多文件处理
- 如果静态库需要暴露出来的 .h 比较多,可以考虑创建一个主头文件,一般 主头文件 和 静态库 同名),在主头文件中包含所有其他需要暴露出来的 .h 文件
- 无论是 .a 静态库还是 .framework 静态库,最终需要的都是:二进制文件 + .h + 其它资源文件
- .a 和 .framework 的使用区别
- .a 本身是一个二进制文件,需要配上 .h 和 其它资源文件 才能使用
- .framework 本身已经包含了 .h 和 其它资源文件,可以直接使用
- 图片资源的处理
- 如果静态库中用到了图片资源,一般都放到一个bundle文件中,bundle名字一般跟 .a 或 .framework 名字一致
- bundle的创建:新建一个文件夹,修改扩展名为 .bundle 即可,右击bundle文件,显示包内容,就可以往bundle文件中放东西
实战问题整理:
- 我明明已经设置了build Active architetcture Only -> Debug ->设置为NO了,但是我查看我打包的静态库,为何只支持arm64 没有armv7呢? 或者模拟器只有x86_64,没有i386呢?
- 因为iOS11支持>iPhone 5s,你的静态库当前支持的系统,是>大于iOS11的,因此,静态库,只有arm64架构,同理模拟器只有x86_64架构
- 解决办法,在target中选择相应的静态库,然后在general中把最低支持系统设置的低一点,就行了
- 如下图:
- 如果当前打的静态库(.a或者.framework)中使用了其他的静态库framework(注意不是.a),那么在你这个静态库
Target
下(a形式: general->Linked Frameworks .../framework形式:Build Phases->Link Binary With Libraries ... )
也要导入这几个使用的framework- 注意:如果是
.a
的话,可以直接拖入到这个静态库文件中,如果不拖入,注意这里不能导入.a的静态库
- 注意:如果是
- 注意事项:
- 含有静态库源码的项目是不可以打成ipa包的!!!!
- 如何查看一个framework是动态库还是静态库?
- 使用file命令:
file framework中的二进制文件
- 如果是动态库,会有
dynamically
字段。
- 使用file命令:
bundle资源文件的应用
- 如果有代码里用到了image和xib或者sb文件,那么此时就要用到bundle了
- 创建bundle
-
如下面6步骤
- 为何选择macOS ? 因为iOS环境下没有
- 命名(假设为testBundle),然后create
- 项目目录会多一个testBundle文件
-
- 配置bundle
- 因为当时选择的事macOS,所以现在要设置为iOS类型的
- 要把enable bitcode设置为NO
-
设置COMBINE_HIDPI_IMAGES 为NO
- 在General中设置当前最低支持的iOS系统
- 在项目的testBundle中添加相应的图片、xib、sb,然后项目选择为当前的bundle的Target编译即可,就能在项目的products文件下找到相应的bundle文件。
- 注意:
- 一定要在testBundle这个target下的Copy Bundle Resource中看看,相应的资源是否已经添加进去了。同理在打xxx.framework静态库的时候一定也要看看Compile Sources 、Headers 、Copy Bundle Resource文件下是否都添加了相应的文件。
- 如果静态库xxx.framework用到了这个bundle,注意一定要将这个bundle添加到该静态库的Copy Bundle Resource 中,这个很重要!!!!。
- 如果没有添加,最后打包成的静态库xxx.framework中就不含testBundle.bundle文件
- 如何添加呢?
- 在项目的TARGETS列表中选择xxx这个静态库Target
- 选择Build Phases-> Copy Bundle Resources
- 点击+,选择当前项目下的Products文件下的testBundle,添加即可
- 这样打出来的静态库xxx.framework中,就包含testBundle.bundle文件了
- 但是注意了:编译xxx.framework静态库之前,必须先编译testBundle.bundle,否则就会报错
-
项目中一定也要把这个bundle添加到Copy Bundle Resource文件下,否则下面的代码找不到bundle !!!!
NSBundle *bundle = [NSBundle bundleWithPath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"CMZHReadIDCardBundle.bundle"]];
加载bundle中的资源
- 图片资源
-
代码
//方法1: NSString *path = [[NSBundle mainBundle] pathForResource:@"myBundle" ofType:@"bundle"]; NSBundle *bundle = [NSBundle bundleWithPath:path]; NSString *file = [bundle pathForResource:@"pic" ofType:@"png"]; UIImage *img = [UIImage imageWithContentsOfFile:file]; //方法2: NSString *file2 = [[NSBundle mainBundle] pathForResource:@"myBundle.bundle/pic" ofType:@"png"]; UIImage *img2 = [UIImage imageWithContentsOfFile:file2]; //方法3: UIImage *img3 = [UIImage imageNamed:@"myBundle.bundle/pic"];
-
xib、sb中使用图片:直接设置即可,因为打包bundle的时候xib、SB文件和图片资源文件在同一目录下.
-
- 代码加载xib/sb
-
xib
//重写要加载的view的init方法 - (instancetype)init{ NSBundle *bundle = [NSBundle bundleWithPath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"CMZHReadIDCardBundle.bundle"]]; if (self = [super initWithNibName:@"ShowIDCardInfoViewController" bundle:bundle]) { } return self; }
-
sb:
//新增类工厂方法 +(instancetype)faceRegisterVC{ NSString* bundlePath = [[NSBundle mainBundle] pathForResource:@"xxbundle" ofType:@"bundle"]; NSBundle* resourceBundle = [NSBundle bundleWithPath:bundlePath]; UIStoryboard *sb = [UIStoryboard storyboardWithName:@"FaceRegisterStoryboard" bundle:resourceBundle]; return [sb instantiateViewControllerWithIdentifier:@"FaceRegisterStoryboard"]; }
-
静态库的融合
- 情景:
- 当我们需要通过一个静态库,自己做一个封装,然后生成一个新的静态库
- 合成静态库的常见类型
- 类型一:原有静态库+新的静态库一块提供给别人
- 我们自己开发的静态库,用到了别人的静态库,然后生成一个自己的开发的静态库。
- 打完静态库之后,我们要提供给接入方的静态库有: 自己的静态库+别人的静态库
- 类型二:只提供自己的静态库,而且这个静态库内部用了别人的静态库
- 场景一:当别人的静态库是以.a的形式提供时,我们就可以直接将.a静态库拖入到我们自己建的静态库文件夹中,然后打出一个新的静态库。给别人提供时,只需要提供自己打的新的静态库即可。
- 场景二:当别人的静态库是以.framework方式提供时,我们就不可以直接将.framework文件拖入到我们自己建的静态库文件夹了,那么怎么融合呢?
- 打开framwork,找打静态源文件,将源文件后缀名改为.a
- 将header文件夹也拿出来
- 新建一个文件夹,将.a与headers内容放到这个新的文件夹中。
- 这么就是一个.framework文件转化为.a静态库形式了。
- 这么就可以按照场景一融合了。
- 类型一:原有静态库+新的静态库一块提供给别人