iOS静态库的那些坑

最近项目需要我开发一个内嵌iOS的SDK,直白说就是要写一个通用的静态库。很久以前我也做过类似的事,不过由于不是商用的东西,没有深入研究,但这次不一样了。原以为很简单的东西,最后我还是踩了不少的坑,所以特此纪录一下。

一、基本背景

网上有很多介绍静态库的资料,基本的步骤我就不重复叙述,这里的介绍很详细:http://www.cocoachina.com/applenews/devnews/2013/1204/7468.html

我只重复几个关键点:

  1. 静态库所依赖的dylib或者framework,最终使用静态库的程序也需要引用。
  2. 静态库是二进制代码,区分处理器类型的,可以使用lipo -create –output创建支持多处理器的静态库。(详情可以参照我之前博客转载的命令列表https://pinka.cn/2013/08/ios常用静态库操作命令
  3. 编译时请把Build Configuration选择为Release
  4. Objective-C无真正私有方法,所以你静态库想要暴露多少接口,多少类,完全取决于你的头文件。

二、命名问题

iOS程序员大多都会使用第三方的开源代码,对于直接在应用中使用这些代码,一般是不会对其做出什么修改的。但是静态库不一样,在静态库中使用开源代码必须要注意几件事。

  1. 类的命名:

    由于Objective-C没有命名空间,所以定义名字相同的类会视为重复定义。因此使用开源代码的时候,必须要对其类名进行修改。我建议是加上三位字母以上的前缀,否则还是有可能重复的。

  2. Category:

    作为Objective-C的一个重要特性之一,开源代码也用了不少category。当然,category添加的方法即使名字重复 了也不会报错,但是会存在其中一个方法覆盖了另一个。由于开源代码也存在版本不同的问题,因此为了避免造成混乱,建议还是给category添加的方法添加前缀。Category 除了可以添加方法,还可以重写方法,但对于在静态库中使用category重写和method swizzling改变行为的代码,我建议还是敬而远之。这对静态库使用者会造成很多迷惑,特别一旦出现与预期不符的情况时,基本上是难以找出原因。当然,如果是有非常特殊需求的时候,这些要求也是可以放松的,不过必须要在嵌入文档中说明。还有一点需要说明的是,使用category重写的类load方法,是允许的。因为,无论用多少个category,加了多少个load方法,这些方法都会一一被执行。至于执行顺序是不保证的,详情可以自行google。

  3. 全局变量和函数:

    全局变量重名是必然编译失败的,这是C时代的产物,因此也是需要添加前缀来避免重名。使用static修饰的文件作用域变量可以不用修改,(请了解好C的作用域基础,以及extern、static),C函数也是同样道理。

三、调试log

  1.  预处理:

    很多开源库都会在debug时打开log,release关闭log。发布到store的应用不应该把调试的log都打印出来。因此在打包静态库 时,把Build Configuration选择为Release一般可以把多数开源库的log都去掉了。原理是利用预处理检测“DEBUG”标记来去掉log代码。但是 如果静态库使用者希望能在开发时看到log,那就只能分开打包debug和release的包了。

  2. 运行时:

    还有一种方法是在运行时判断是否附加了调试器,苹果官方提供了一个方法检测https://developer.apple.com/library/mac/qa/qa1361, 这样就可以打出一个只在调试时才会打印log的包。但是,即使如此我也建议不宜多用,因为这需要运行时判断debug状态,也算是有额外的开销(当然可以 用静态变量纪录,减少重复调用函数的开销)。而且,调试内容写入发布的应用,反编译也是可以导出相关信息。所以,需要斟酌使用。

  3. 宏:

    对于那些习惯使用__func__,__FILE__,__LINE__等宏的程序员还是注意一下,这些宏编译后是会变成字符串或者数字的,因此暴露了原文件的信息。所以,发布的包建议去掉这些信息。

四、静态库冲突

上面说了那么多,都是为了写出一个规范的静态库。不过很可惜,你只能控制你力所能及的部分,对于别人的代码你很难去干涉。例如,你极有可能碰上了两个静态库 发生冲突的情况。一般情况下,你可以建议其中一个静态库的所有者改进他的代码,但是也有可能这些库都没人维护或者他们拒绝修改。但项目开发必须要用到,遇 到这种情况只能把其中一个冲突的静态库做出改造了。

  1. 利用lipo –info查看静态库包含的包,然后全部分离出来。其中有个小问题就是对于armv7s等处理器,lipo识别不好,所以需要改为调用“xcrun -sdk iphoneos lipo”。
  2. 利用ar解包其中一个静态库,然后把发生冲突的.o文件删除,然后用libtool重新打包。
  3. 最后对每种处理器重复步骤2,然后用lipo重新合并静态库,最后替换原来的静态库。

(以上命令可以参照https://pinka.cn/2013/08/ios常用静态库操作命令

不过这么做有个不稳定的因素的是,那个被替换掉的.o有可能版本和现有的不同,又有可能被添加了自有方法等。对于这类情况,我也无能为力了,遇上这么坑人的静态库,只能束手无策。

五、静态库快速改成framework

Xcode没有自带打包framework的工程模板,当然网上有很多方法可以添加framework的模板。但对于现有的静态库,其实也可以快速改造成framework。

首先需要简述一个概念,对于iOS的应用,第三方的framework和系统提供的framework是有区别的。第三方的framework实际上是静态 嵌入到应用,也就是和静态库无异。而系统的framework是动态库,也就是运行时才加载的,不会嵌入到应用。AppStore理论上是不会让使用第三 方动态库的应用通过审核的。

因此我们使用的第三方framework其实本质上就是静态库,所以才使这个技巧能成功应用。

  1. 首先新建一个“XXXXX.framework”的文件夹
  2. 把.a文件拖进去并重命名为“XXXXX”(不带扩展名)
  3. 在framework文件夹内新建文件夹“Headers”,把头文件拖进去。

就这样,一个“伪”framework就完成了,其实和.a文件相比只是文件组织形式上好看了,没很大实际意义。

发表评论