iOS中的库分为两种类型:
静态库:.a和framework
动态库:.dylib 和.framework

  • .a 是一个纯二进制文件,.framework 除了二进制文件还有其他资源文件,.a不能直接使用,必须有.h文件的配合,而.framework则可以直接使用。
    可以理解为 .a +.h +sourceFile = framework
  • .a文件肯定静态的,.tbd肯定是动态库,.framework 可能是静态库也有可能是动态库。
  • 深究的话.a文件可以理解成一种归档文件,其中存储的是经过编译的.o格式的目标文件。
  • 静态库在链接时,会被完整地复制到可执行文件中,如果多个app使用了同一个静态库,那么每个app都会拷贝一份。
  • 动态库不会复制,只有一份,程序运行时动态的加载到内存中,系统只会加载一次,多个程序共用一份。
  • 但是项目中国使用了自己定义的动态库,苹果是不允许上架的,在iOS8.0以后苹果开放了动态库加载.tbd的接口,用于挂在.tbd动态库。

对静态framwork做如下操作:

  1. Dead Code Stripping 设置为NO: 如果开启此选项则会对“dead”、“unreachable”的代码进行过滤
  2. Link With Standard Libraries设置为NO :避免重复链接
  3. Mach-O Type设置为Static Library
  4. iOS Deployment Target静态库需要支持的版本。
  5. 在编译库的时候,xcode不支持同时编译模拟器和真机架构,需要选中Generic iOS Device和任意一个模拟器各编译一次。针对两个不同架构的.a进行合成(lipo -create第一个.a文件的绝对路径 第二个.a文件的绝对路径 -output 最终的.a文件路径)
  6. 在项目中导入同时支持模拟器和真机的.a文件和暴露的.h文件。 需要注意的是提供给使用者的话,光.a文件是不能够编译成功的,还需要提供头文件给使用者导入

使用framework

  1. 如果打包的文件中设置图片的地方,图片最好单独打包一个bundle,这个时候图片的引用不再是mainBunle。
  2. 在Build Phases的Headers中,将需要公开的头文件添加到public。并且在frameworkXX.h中国也要引入需要公开的头文件。
  3. 同样也要修改Build Active Architecture Only 修改为NO,否则生成的静态库就支持当前选择的设备结构。

动态库

关于动态库和静态库的比较,对于使用者而言,导入动态库唯一要注意的是需要添加对应的动态库到工程里面。

动态库的出现可能的原因是因为Extension的出现,Extension和APP是两个分开的可执行文件,同时需要共享代码,这种情况下动态库的支持是必不可少的。

这种动态framework和系统的UIKit.Framework还是有很大区别的,系统的库不需要拷贝到目标工程中,我们自己做出来的framework如果是动态的,最后也还是要拷贝到App中(APP和Extension的bundle是共享的),因此苹果又把这种framework称为Embedded Framework

在Xcode9之前官方不支持swift静态库,如果是swift项目必须采用user_framework!。

CocoaPods
对于目前采用模块化管理,一般都是使用CocoaPods的方式来管理代码。CocoaPods现在同时支持static library 和framework 的依赖管理。 在使用Cocoapods管理的项目中增加了一下目录文件:

  1. PodFile依赖描述文件
  2. Podfile.lock当前安装的依赖库的版本
  3. xx.xcworkspace 使用cocoapod管理依赖的项目,Xoce只能使用workspace编译项目。
    xcworkspace文件实际是一个文件夹,实际workspace信息保存到contents.xcworkspacedata里。
  4. Pods.xcodeproj,所有的第三方库都由pods工程构建,每一个第三方库都对应Pods工程的一个targets。 在Target Support Files目录下每一个第三方库都会有一个对应的文件夹,比如AFNetworking,该目录下有一个空实现文件;预定义文件用来优化头部文件编译速度;xcconfig文件在工程配置中使用,主要存放头文件搜索目录,链接的Flag(比如链接哪些库)。
    在Target Support Files的目录下还会有一个Pods-XXX的文件夹,该文件夹下存放了第三方库声明文档markdown 文档和plist;还有一个dunmmy的空实现文件;以及debug和release各自对应的xcconfig配置文件;另外还有两个脚本文件,frameworks.sh用于实现framework库的链接,当依赖的第三方库是framework形式才会用到该脚本,resources.sh用于编译storyboard类的资源文件或者拷贝*.xcassets之类的资源文件。

Pods工程配置 Pods工程会为每一个依赖的第三方库定义一个Target,还会定义一个Pods-XXX的Target,每个Target会生成一个静态库。 Pods工程会新建Debug和Release两个Configuration,每一个Configuration会为不同的target设置不同的xcconfig,xcconfig指出了头文件查找目录、要链接的第三方库、链接目录等信息。

CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_LDFLAGS = -framework "CoreGraphics" -framework "MobileCoreServices" -framework "Security" -framework "SystemConfiguration"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/AFNetworking
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
复制代码

OTHER_LDFLAGS指明了链接的framework。

在主工程中的Build Phases 中有一项安装pod的framework,会调用Pods-xxx-frameworks.sh

经常遇到的linker flag 问题

.a其实是编译好的目标文件的集合,问题出在链接一步,而不是编译的问题,在Objc中使用静态库时,需要知道哪些文件需要链接进来,它依据的是 __.SYMDEF SORTED文件。
可惜文件不会包含所有.o文件,而只是包含了定义了类的目标文件,可能并没有包含拓展类的信息,这样的情况下拓展类Extension虽然存在,但是并没有被链接到最终的可执行文件中,从而导致找不到方法的错误。

解决方法是在调用者的Build Settings中找到other liner flag(OTHER_LINK_FLAG) 并写上-Objc选项,不管是否被引用到,链接器会把 Objective-C 的类和分类的所有对象文件全部链接,注意的是如果只有分类没有类,即使加了-Objc选项也会报错,这个时候应该使用-force_load参数,但这个情况一般不会出现。

但是Workspace有很大的好处,Xcode会自动编译被依赖的静态库。这也就是为什么我们在pod install之后不需要配置相关设置,直接build主项目即可。

在纯Objc的项目中,CocoaPods使用编译静态库.a方法将代码继承到项目中。在Pods项目中的每个target都对应这一个Pod的静态库。不过在编译过程中并不会真的产出.a文件。
对于swift项目,Cocoapods提供了动态framework的支持,通过user_framework! ,对于swift写的库,想通过Cocoapods引入工程,必须加user_framework!

CocoaPods作为OSX和iOS开发平台的类库管理工具,Cocoapods会自动处理库的依赖,减少各种过配置。不需要手动编译第三方库,直接编译主工程即可,隐式地指定了编译顺序。

静态库不会递归引用(比如A静态库引用了B静态库,A编译后的静态库并不会包含B静态库,如果想使用A静态库,使用者还需要导入B静态,否则就会报‘Undefined symbol’错误),所以手动导入静态库的话,使用起来会比较麻烦。

Cocoapods有一套它自己的版本表示规则,也可以自动分析依赖关系,既然静态库只是目标文件的打包形式,那么我们只需要找到被嵌套的静态库,并拿到其目标文件,然后和完成的静态库放在一起重新打包即可,如下操作,最终得到对外发布A并集成了B。

lipo A.a -thin x86_64 output A_64.a # 如果是多 CPU 架构,先提取出某一种架构下的 .a 文件  
lipo B.a -thin x86_64 output B_64.a  
ar -x A_64.a # 解压 A 中的目标文件  
ar -x B_64.a # 解压 B 中的目标文件  
libtool -static -o Together.a *.o # 把所有 .o 文件一起打包到 Together.a 中
复制代码

可能在使用ar命令的时候会遇到如下问题:

ar: libstaticDemo.a is a fat file (use libtool(1) or lipo(1) and ar(1) on it)
ar: libstaticDemo.a: Inappropriate file type or format
复制代码

出现上面这个问题是因为.a是支持在不同架构的运行的合成,也就是合体之后的静态库,

lipo -info xx.a
lipo -thin x86_64  libstaticDemo.a -output x86_64libDemo.a 
复制代码

查看支持的哪些架构,若是支持多种架构,则需要拆分,这样使用ar就不会有问题了。

基于pod自动创建:pod lib create 库名 在podspec中,需要注意的是 依赖项不仅要包含自己类库的依赖,还要包括所有第三方库的依赖,只有这样当你的库打包成.a 或.framework 时才能让其他项目使用。
打包类库

sudo gem install cocoapods-packager

pod package xxx.podspec --library --force

复制代码

其中--library 指定打包成.a文件,如果不带上将会打包成.framework 文件,--force是指强制覆盖。
该插件通过对引用的三方库尽心重名很好的解决了类库命名冲突问题。

cocoapods 1.4.0.2-beta支持打包静态库,但需要在podspec中添加属性static_framework = true。

模拟器32/64分别对应i386/x86架构 真机:armv7,armv7s,arm64

工具:
lipo

查看当前静态库支持的CPU架构

lipo -info xx.a  
复制代码

合并静态库

#lipo -create 静态库存放路径1  静态库存放路径2 ...  -output 整合后存放的路径
lipo -create xx1.a xx2.a -output xx3.a
复制代码

拆分

#lipo 静态库源文件路径 -thin CPU架构名称 -output 拆分后文件存放路径
lipo xxx3.a -thin armv7 -output xx2.a
复制代码