先来看几个概念定义:
- 什么是库?
库是共享程序代码的方式,一般分为静态库和动态库。 - 静态库和动态库的区别?
静态库:链接时完整地拷贝至可执行文件中,呗多次使用就有多分冗余拷贝
动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存 - iOS里静态库的形式?
.a 和 .framework - iOS动态库的形式?
.dylib 和 .framework - framework为什么既是静态库有事动态库?
系统的.framework是动态库,我们自己建立的.framework是静态库 - .a 和 .framework有什么区别?
.a 是一个纯二进制文件,.framework中除了有二进制文件之外还有资源文件
.a 文件不能直接使用,至少要有.h文件配合,.framework文件可以直接使用
.a + .h +sourceFile = .framework - 为什么使用静态库?
方便共享代码,便于合理使用。(懒逼格高)
实现iOS程序的模块化。可以把固定的业务模块化成静态库。(懒高大上)
和别人分享你的代码库,但不想让别人看到你代码的实现。(怕被喷)
开发第三方SDK的需要。
我们可以看出.a的封装和.framework的封装差不多,也有模拟器和真机合并的过程,通过上边的图片我们可以看出.a 和.framework的区别,就是.a+.h+soureFile=.framework。可以看出我们直接封装.framework其实是最好的。不啰嗦了,先来看看.a文件是怎么封装的
一 .a文件的封装
(一)创建一新工程TestSDK,选择Framework & Library中的 Cocoa Touch Static Library,如图1
只留下.h文件就可以。
(二)创建你要封装的模块,由于本文主要是讲述制作流程这里我直接拖入一个模块如图2:
在TestSDK.h中引入你的控件的头文件ChannelAlert.h即可。
(三)分别选择模拟器和真机编译工程,在Products下会生成.a文件,show in finder会发现如图3两个.a文件
iphoneos 适用于真机,iphonesimulator适用于模拟器。打开终端合并两个文件成为通用版本,命令
lipo -create
模拟器.a文件目录
真机.a文件目录
-output 输出目录/文件
如图4:(嘿嘿是不是很红)
看到libTestSDK.a了吧,这就是通用版本的.a文件
(四)使用方法:
将此文件夹拖入到你需要使用的工程中,在需要的地方导入头文件TestSDK.h如图6:
功能按照你正常的使用方式就可以了。看看我的效果
到此简单的.a文件制作完成了,下面我们来看看更好的framework是如何制作的
二 .framework文件的封装
(一)创建工程TestFramework,如图F1:
(二)创建自己的类,Tool.h,Tool.m,如图F2:
如果有依赖库就要添加依赖库(本文重点是流程,这里就不多加赘述)。在TestFramework.h中#import "Tool.h"。
(三)在Build Phases->Headers中设置你要暴露的接口,主要设置Public和Private,这里我把Tool.h移动到了Public中如图:
选择模拟器和真机,分别编Command+B一次,
iphoneos 对应的是真机,iphonesimulator对应的模拟器
下面我们将两个framework合并,要用到脚本语言
if [ "${ACTION}" = "build" ]
then
INSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME}.framework
DEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework
SIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
#ditto "${DEVICE_DIR}/Headers" "${INSTALL_DIR}/Headers"
lipo -create "${DEVICE_DIR}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"
#open "${DEVICE_DIR}"
open "${SRCROOT}/Products"
fi
如图:在4位置添加上面的脚本,然后Run(xcode左上角的黑三角)之后会弹框在Products里面就可以看到TestFramework.framework了。
(四)使用方法
在你需要的地方导入头文件:如图
至此,简单的.framework就算是制作完成了。这也仅是一个流程,毕竟framework博大精深。写一点注意事项吧
1、.h文件的外漏一定要保证是自己的想要外漏的。不想外漏的就别外漏了。
2、开始打包的时候,一定要在选中模拟器和选中真机上边分别编译一次, 我觉得之前在家里没有真机的时候编译的好像不对。
3、终端也可以合并(这里就不写了,有兴趣的可以自己搜一搜),在终端上边合并的时候可能是error并生成一个.lipo文件,不要怕,大胆修改成同名的不挂后缀的同名文件。
4、调用的时候分清楚是类方法还是实例方法,方便调用。
5、在制作framework或者lib的时候,如果使用了category,则使用改FMWK的程序运行时会crash,此时需要在该工程中 other linker flags添加两个参数 -ObjC -all_load。(这点没有亲测)
6、带有图片资源的需要把图片打包成Bundle文件,和framework一起拷贝到相应的项目中。
7、公开的类中如果引用的private的类,打包以后对外会报错,找不到那个private的类,可以把那个private的.h放到(也没亲测)
8、**namespace 冲突。**静态库用了某第三方库,项目也用了同样的第三方库,在编译的时候就会有 duplicate symbol 错误,因为有两份同样的第三方库。解决办法就是把用到的第三方库加上自定义前缀,包括类名、delegate 协议、常量名,尤其需要注意 Category 的方法名要修改。
9、封装静态库的时候应尽量避免引入重量级第三方库,**多自己进行封装**。
10、一个静态库要**有自己独有的前缀**,所有类名、常量等都要加同样的前缀。
11、**真机+模拟器支持**。(和第2条意思一样)Xcode 默认只会用当前环境(真机或模拟器)生成静态库,这样的 SDK 不方便其他项目开发时调试。解决办法就是通过脚本生成一份通用库,build_universal_library.sh,via SO.
12、**文档**。静态库的方便是使用者直接拿你提供的方法来用,无需关注具体实现;不方便在于看不到实现,出现问题无法排查,因此需要把 SDK 的版本、更新历史、使用、FAQ 等写成文档,方便使用,也显得 SDK 比较正式规范。
13、图片等资源文件用 **bundle** 方式打包。一个简单制作 bundle 的方法:新建文件夹,重命名为 YourSDK.bundle,然后 Show Package Contents 打开,加入图片。使用图片的时候需要指明 bundle: [UIImage imageNamed:@"YourSDK.bundle/img.png"]。也可以用 Target 方式制作 bundle。
14、如果 SDK 有用到 **Category**,注意项目设置 Other Linker Flags 添加 -ObjC。
编译过程:
从C代码到可执行文件经历的步骤是:源代码 > 预处理器 > 编译器 > 汇编器 > 机器码 > 链接器 > 可执行文件
在最后一步需要把.o文件和C语言运行库链接起来,这时候需要用到ld命令。源文件经过一系列处理以后,会生成对应的.obj文件,然后一个项目必然会有许多.obj文件,并且这些文件之间会有各种各样的联系,例如函数调用。链接器做的事就是把这些目标文件和所用的一些库链接在一起形成一个完整的可执行文件。Other linker flags设置的值实际上就是ld命令执行时后面所加的参数
下面逐个介绍3个常用参数:
-ObjC:加了这个参数后,链接器就会把静态库中所有的Objective-C类和分类都加载到最后的可执行文件中
-all_load:会让链接器把所有找到的目标文件都加载到可执行文件中,但是千万不要随便使用这个参数!假如你使用了不止一个静态库文件,然后又使用了这个参数,那么你很有可能会遇到ld: duplicate symbol错误,因为不同的库文件里面可能会有相同的目标文件,所以建议在遇到-ObjC失效的情况下使用-force_load参数。
-force_load:所做的事情跟-all_load其实是一样的,但是-force_load需要指定要进行全部加载的库文件的路径,这样的话,你就只是完全加载了一个库文件,不影响其余库文件的按需加载
研究不深,欢迎各位来指点。