前两天因为公司业务上的需要,我们需要和联通公司那边进行业务集成,考虑到一些业务上的隐私性,我们将提供的内容打包成了一个静态库,只为对方提供了一些接口,今天正好有空整理一下,填一下这个过程中遇到的一些坑,以方便日后查阅。
什么是库?
库是程序代码的集合,是共享程序代码的一种方式。我们根据源代码的公开情况,可以将库分为 2 种类型
开源库:公开源代码,我们能看到代码的具体实现,比如SDWebImage、AFNetworking等;如何将自己的代码发布到代码托管平台中,然后借助CocoaPods供别人使用,我们在上一篇文章中已经讲过,这里不再细说。
闭源库:不公开源代码,是经过编译后的二进制文件,看不到代码的具体实现。闭源库主要分为:静态库、动态库
静态库和动态库的存在形式以及使用上的区别
存在形式:
静态库:.a 和 .framework
动态库:.dylib 和 .framework
区别:
静态库:链接时,静态库会被完整地复制到可执行文件中, 被多次使用就有多份冗余拷贝 ,如下图:
静态库
动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,以节省内存,具体形式如下图所示:
动态库
静态库的使用场景:
项目开发中我们经常使用到的极光推送、百度地图、友盟分享等等。这些大公司都有自己的核心业务,同时又希望我们去使用他们提供的技术,但是又不想暴露他们的代码,因此他们采用"闭源"的方式来让我们集成。
将MRC的项目放到ARC环境下,我们可以如下图去操作
MRC->ARC
但是如果文件特别多,我们这样一个个去操作是不是很麻烦?其实我们可以将MRC的项目,打包成静态库, 可以在ARC下直接使用, 不需要转换,非常方便;
知识准备:
我们都知道模拟器下的静态库和真机下的静态库是不能混用的,那为什么呢?其实主要原因是模拟器和真机的CPU架构不一样(各个模拟器型号之间架构也不一样)不同机型的CPU, 对应的架构不同;
模拟器:
4s和5使用的是i386
5s-6sPlus使用的是x86_64
真机:
3gs---4s :armv7
5/5c :armv7s(armv7兼容armv7s)
5s---6sPlus:arm64
我们可以分别选中不同的模拟器, 进行编译,然后利用lipo -info xxx.a查看静态库所支持的架构, 如下图所示:
查看静态库支持架构
因为CPU架构的不同,这就造成了一个问题,如果我是在6s环境下编译生成的静态库运行到5上面就会编译不通过, 怎样可以一次编译支持多个架构的的静态库呢?
可以通过Build Settings -> Build Active -> NO,表示不止编译活跃的架构, 让所有的架构都编译,如下图所示:
编译所有架构
.a静态库的制作步骤
新建项目-> 选择 “Cocoa Touch Static Library”
新建静态库
添加库需要包含的源代码,并且设置需要暴露的头文件:
3.png
引入制作的静态库所需要的网络框架(有些会自动引入,有些需要手动引入,根据编译报错,可以检查。)
引入框架
设置支持所有架构:
设置支持架构
编译前检查一下是debug模式还是release模式,选择release模式。在模拟器和真机中分别编译后,libzpstaticLibrary.a就由红色变成了黑色,Show in Finder在目录中查看:
Release模式
.framework静态库的制作步骤
新建项目-> 选择 “Cocoa Touch Framework”
Framework
添加库需要包含的源代码,并且设置需要暴露的头文件:
暴露头文件
如果用户需要导入的头文件过多怎么办?我们可以使用一个主头文件包含其他头文件, 让用户只导入一个主头文件。
编译时, 设置编译所有架构
设置编译架构
因为默认制作的是动态库,我们 需要设置链接类型target -> Build Settings-> 搜索 Mach-o Type ;改为静态库
设置类型
如果没有这一步,我们在测试的时候会报一个错误:
错误提示
这个错误是因为我们默认生成的是一个动态库,如果我们把它当成一个动态库来用的话 就需要在测试工程中General->Embedded Binaries中导入我们这个动态库,这样就不会报这个错误了。
动态库
经过以上步骤后我们就可以成功的生成了一个.Framework的静态库或者是动态库了。
静态库的操作
合并静态库
因为静态库针对于模拟器和真机生成了不同版本(支持不同架构), 所以没法同时运行,但是我们可以将二者进行合并,合并后的.a大小大约是不合并的2倍左右。
cd 到一个目录,然后通过
lipo -create Debug-iphoneos/libTools.a Debug-iphonesimulator/libTools.a -output libTools.a
进行合并,合并后的架构如下图所示:
分解合并库
既然有合并静态库,那么对应的也有分解合并库,例如我们在发布的时候只想要使用arm64架构,那么我们可以通过下面这行命令来达到我们目的:
lipo -thin arm64 静态库 -output 新的静态库名称
如下图所示:
查看分解之后的库可以发现该库已经非常小了
从合成库中移除某个架构
如果合成库中的某一个架构我们用不到了,想要移除,那么我们可以通过下面的命令,例如我们想要将合成库中的i386移除
lipo -remove i386 静态库 -output 静态库名称
执行命令后我们可以发现,合成的静态库中已经没有了i386这个架构了
总结
.a静态库和.framework静态库的区别?
我们都知道静态库包括.a和.framework,那么二者之间到底有什么区别呢?
.a是一个纯二进制文件, 而.framework中除了有二进制文件之外还有资源文件,比如Bunle、Plist等。
.a文件不能直接使用, 至少要有.h文件的配合;.framework文件可以直接使用
.a + .h + sourceFile = .framework
建议使用.framework
静态库的调试
因为静态库只提供了一些头文件,实现代码都是经过二进制化的,如果我们想对静态库进行断点调试如何做呢?
我们可以创建复合项目来对静态库进行调试:
创建一个测试工程,选中TARGETS,点击下面的➕按钮,添加一个静态库,如下图所示
复合项目
设置主工程文件和静态库的依赖关系。
添加依赖关系
经过以上两步后我们就可以对要制作的静态库文件进行调试开发了,是不是很爽,至于制作静态库就和上面的步骤一样了。
Bundle封装XIB、图片等资源文件
创建Bundle文件
因为我是新建了一个复合测试工程,所以在这里为了方便我们直接在复合测试工程中添加了一个子工程,因为iOS框架中没有bundle,要选中macOS框架找到bundle,如下图为我们的Bundle命名为STWNetPay
image
Bundle的本质其实是一个目录,我们完全可以创建一个文件夹,然后把需要封装的资源文件拷贝到该目录下,然后将后缀名为改成bundle即可,但是如果我们想要将XIB文件封装到Bundle文件中的话,这种方法就不可以了. 考虑到xib是文本文件,编译后要被序列化为二进制的nib文件,使用时将nib文件反序列化,就可以正常显示界面了。而bundle本身是静态的,其内部的资源包不参与项目的编译,所以,此处必须创建工程把xib序列化为二进制的nib,否则的话,直接创建文件夹后改名就可以了。
设置Bundle
1.创建好之后我们发现里面只有一个Info.plist文件,经测试该文件不能删掉,如果删掉之后编译Bundle文件会报错:bundle format unrecognized, invalid, or unsuitable
2.iOS Deployment Target改为你支持的最低版本
image
3.设置base SDK 为latest iOS
image
4.COMBINE_HIDPI_IMAGES设置为NO
因为iOS创建Bundle时放入的图片资源(.png)在默认配置下会被转为.tiff格式,使用的时候找不到。所以找到bundle的工程,Buld Settings > COMBINE_HIDPI_IMAGES设置为NO
image
5.Skip Install 设置为NO
image
6."Build Active Architecture Only" 设置为 NO
向Bundle中添加资源文件
image
一切就绪,按快捷键"Command + B"编译,编译成功后,我们的Bundle文件即由红色变成了黑色,表示编译成功,这个时候找到我们的Bundle文件,显示报内容,如下图所示:
image
可以看到XIB文件经过编译已经变成了nib文件,这就是我们想要的.
使用bundle里的资源文件
将编译好之后的Bundle文件拖入我们的静态库中,因为是要在静态库中使用Bundle文件,而不是在MainBundle中,所以我们需要先找到我们Bundle所在的路径才能使用它里面的资源.
创建一个管理Bundle路径的类BundleTools,专门用来获取我们Bundle所在的路径:
#import<Foundation/Foundation.h>#define BUNDLE_NAME @"STWNetPay"@interfaceBundleTools:NSObject+ (NSString*)getBundlePath: (NSString*) assetName;+ (NSBundle*)getBundle;@end
#import"BundleTools.h"@implementationBundleTools+ (NSBundle*)getBundle{NSURL*url= [[NSBundlebundleForClass:[selfclass]] URLForResource:BUNDLE_NAME withExtension:@"bundle"];NSBundle*bundle = [NSBundlebundleWithURL:url];returnbundle;}+ (NSString*)getBundlePath: (NSString*) assetName{NSBundle*myBundle = [BundleTools getBundle];if(myBundle && assetName) {return[[myBundle resourcePath] stringByAppendingPathComponent: assetName]; }returnnil;}@end
使用图片
UIImage*image = [UIImageimageWithContentsOfFile: [BundleTools getBundlePath:@"nav_back"]];
使用XIB
控制器是XIB的情况,此时应该在控制器中重写Init方法
- (instancetype)init{NSBundle*myBundle = [BundleTools getBundle];//从bundle中获取界面文件self= [superinitWithNibName: [NSStringstringWithUTF8String: object_getClassName(self)] bundle: myBundle];if(self) {// Custom initialization}returnself;}
否则
NSBundle*myBundle = [BundleTools getBundle];//从bundle中获取界面文件view = [myBundle loadNibNamed:@""owner:niloptions:nil].firstObject;
Swift打包动态库
因为Swift项目是不支持静态库的,必须使用动态库。所以我们在新建一个静态库的时候,一定要选择Cocoa Touch Framework
创建动态库
如果我们在项目中将target -> Build Settings-> 搜索 Mach-o Type ;改为Static Library
静态库
Command+B编译,系统会报一个错,提示我们Swift is not supported for static libraries.如下图:
静态库报错
所以,如果在Swift项目中,我们必须使用动态库;
因为Swift是没有头文件这个说法的,所以在Build Phases中我们也不用设置暴露头文件了
11.png
确定支持模拟器或者真机中的所有架构
编译架构
最重要的,因为swift没有头文件,所以如果我们想让别人用到我们的方法,就用Public修饰符修饰。
暴露方法
这样生成的动态库中就会将我们的Swift方法转换成了OC的方法
SWIFT_CLASS("_TtC10zpSwiftLib4Tool")@interfaceTool:NSObject+ (void)Log;- (nonnullinstancetype)init OBJC_DESIGNATED_INITIALIZER;@end
这样将生成的动态库拖入到我们的测试工程中就可以成功的使用了,但是和静态库有一点不同的是,我们需要在测试工程中General->Embedded Binaries中导入我们这个动态库即可。
链接:https://www.jianshu.com/p/f14553494d88