因为公司最近要把某一个功能模块提供给第三方使用,所以就需要将涉及到的源码打包成静态库(.a文件),但是。。。怎么打包呢,从来没做过啊!谷歌了一上午,找到很多文章,写的都很好,但是很少能有一篇,让你看到就能彻底搞懂的。比如自己源码中包含第三方静态库怎么办?用到的图片资源,Xib资源怎么办?自己源码依赖的第三方库怎么处理才能不影响第三方对相同库的升级及使用?这都是需要考虑的问题。关于静态库的介绍我就不普及了,直接一步步进行.a库的创建:
#一、创建静态库工程
这一步应该是很简单的,比如项目名称取为 YJEChatSDK。
建好之后大概就是这个样子:
把工程里默认添加的类删除(移到废纸篓)就可以了,整个工程很干净!
#二、对工程进行必要的设置
1、支持的系统这里要选择IOS哦(虽然默认就是)。。。
2、Build Active Architecture Only选项,当它设置为Yes时,是为了debug的时候编译速度更快,此时它只编译当前的architecture版本。 而设置为No时,会编译所有的版本。这里我们要设置为NO。
3、如果你的源码中使用了类别(category),就要在 Other Linker Flags 中添加 -ObjC或者-all_load 。
Unix的标准静态库实现和Objective-C的动态特性之间有一些冲突:Objective-C没有为每个函数(或者方法)定义链接符号,它只为每个类创建链接符号。这样当在一个静态库中使用类别来扩展已有类的时候,链接器不知道如何把类原有的方法和类别中的方法整合起来,就会导致你调用类别中的方法时,出现"selector not recognized",也就是找不到方法定义的错误。为了解决这个问题,引入了-ObjC标志,它的作用就是将静态库中所有的和对象相关的文件都加载进来。
本来这样就可以解决问题了,不过在64位的Mac系统或者iOS系统下,链接器有一个bug,会导致只包含有类别的静态库无法使用-ObjC标志来加载文件,变通方法就是使用-all_load 。
4、选择release
到此为止对于工程的基本设置算是完了,当然根据自己的需求也许还有其他的设置。
#三、添加代码文件
注意下,打包静态库的时候并不能包含资源文件,即使我们将资源文件(.png文件或者Xib文件)拷贝到静态库工程中,但实际上这些资源是不会添加到target的,也就是说编译结果中并不包含这些资源,因此如果有人调用你制作的这个静态库,所有的资源(图片、Xib)都是缺失的。
针对这种情况,我们将代码文件和资源文件分开考虑,首先是代码文件的打包:
1、添加自己的代码和私有库
把自己的源码以及私有库(不包括第三方开源库,因为开源库谁都可以去下载使用,没必要也不应该打包进去)放到工程中。
如果你的源码或者私有库又使用了第三方的静态库,打包的时候并不能静态库中包含静态库,所以只需要把第三方静态库的头文件放进我们的工程就好,而不需要将.a文件也添加进来。需要注意的是,最后使用时,要把我们的.a库和第三方的这个.a库一起放进项目。
2、添加依赖的开源库
如果我们的源码依赖一些开源库,比如AFNetWorking,Mansory等,就需要将它们打包进来,不过这样一来,容易给使用者带来问题,比如开源库的冲突,版本不兼容等。
所以我的处理办法是,只添加这些开源库的头文件,然后告诉使用者我们的静态库是依赖这些开源库的,让他们自己下载相应的框架。这样做的好处是,方便第三方使用者随意升级自己的开源库而不用担心会跟静态库中的开源库引起冲突,而且就算使用者升级了开源库的版本,也一般不会改变头文件里面的接口(高版本总会兼容低版本),所以不会影响我们的静态库。
3、暴露出相应的接口(头文件),供第三方使用
我们打包静态库肯定是要给人用的,所以需要暴露出设计好的头文件供别人使用。静态工程里需要编译的所有源文件都会包含在Compile Source
中,如下图所示:
而需要暴露出来的头文件添加在Copy Files
选项中,如下图所示:
到此为止基本上完成了打包的准备工作,下面开始编译。
#四、编译生成静态库
根据需要,如果要在模拟器下使用静态库,编译时就选择模拟器,在真机中使用,编译时就选择真机。
这里我在模拟器和真机下分别运行了一次,得到两个.a文件,真机和模拟器:
一般将静态库给别人使用时,要同时给真机版和模拟器版,给两个文件肯定是不方便的,所以要把两个版本合并为一个.a文件,这样不管真机还是模拟器,都可以运行。
打开终端,可以先查看下.a库支持的架构,输入 lipo -info 静态库路径
,静态库路径这里直接把文件拖进来就好。
很显然,armv7和arm64表示32位和64位真机,i386和x86_64表示32位和64位模拟器(mac的架构)。下面使用命令将真机版和模拟器版本合并:
lipo -create 静态库路径1 静态库路径2 -output /Users/yangjie/Desktop/YJEChatSDK.a
最终得到需要的YJEChatSDK.a文件。
#处理资源文件(图片.png和Xib文件)
*首先仅考虑图片
如果代码中使用了一些图片资源,怎么样才能正确读取这些图片呢?我的做法是(我不太清楚是不是还有其他办法):在桌面新建一个文件夹,将用到的所有图片放进去,然后把文件夹的名字改为:文件名称.Bundle
。比如我这里就改为YJsdkBundle.Bundle
。这样在使用静态库时,直接将该Bundle文件一起放进工程就可以了。
然而这样还是不行的,因为静态库中的代码依然找不到图片资源的路径,所以还是不能正确加载。我们需要重新返回代码,在代码中添加两个宏定义:
define BUNDLE_PATH [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"YJsdkBundle.Bundle"]
define YJImageNamed(imageName) ([UIImage imageNamed:[NSString stringWithFormat:@"%@/%@",BUNDLE_PATH,imageName]])
将所有加载图片的方法[UIImage imageNamed:]
替换为我们定义的宏,比如我这里是YJImageNamed(图片名称)
,需要注意的是,图片名称要跟Bundle文件中图片名称对应(后缀@2x或者@3x不用加)。我这里的Bundle文件内并没有子文件夹,所以宏定义中,stringWithFormat:@"%@/%@",BUNDLE_PATH,imageName
这么写,如果你的Bundle文件下还有子文件夹,应该这么写:
stringWithFormat:@"%@/文件夹名称/%@",BUNDLE_PATH,imageName
这样,图片就可以正确加载了!
关于Xib文件
对于Xib文件,我并没有亲自尝试,所以。。。没有发言权了,大家可以谷歌一下。(__*)
打包好之后,使用时编译连接错误问题:
类似这种奇怪的错误,我遇到了,原因是,1、没有引入必要的系统框架。2、静态库已经包含了某些文件,使用的时候,又引入了这些文件,造成duplicate错误。