iOS专题2:静态库和动态库详解

rjjh3140 8年前

来自: http://www.jianshu.com/p/c8366e4f9378


1.什么是库,为什么使用库?

库是共享程序代码的方式,一般分为静态库和动态库;库实现了iOS程序的模块化,将某些特定的功能模块化为库的格式方便分享和使用!

2.静态库和动态库有什么区别?

静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。

动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。

静态库和动态库都是闭源库,只能拿来满足某个功能的使用,不会暴露内部具体的代码信息,而从github上下载的第三方库大多是开源库

3.这两种库都有哪些文件格式?

静态库:.a和.framework

动态库:.dylib和.framework(系统提供给我们的framework都是动态库!)

注意:两者都有framework的格式,但是当你创建一个framework文件时,系统默认是动态库的格式,如果想做成静态库,需要在buildSetting中将Mach-O Type选项设置为Static Library就行了!



4..a文件和.framework文件的区别?

.a是一个纯二进制文件,不能直接拿来使用,需要配合头文件、资源文件一起使用。

将静态库打包的时候,只能打包代码资源,图片、本地json文件和xib等资源文件无法打包进去,使用.a静态库的时候需要三个组成部分:.a文件+需要暴露的头文件+资源文件;

.framework中除了有二进制文件之外还有资源文件,可以拿来直接使用。


framework文件的内部:黑色的二进制主文件+各种资源文件

5.制作静态库需要注意的几点:

(1)注意理解:无论是.a静态库还.framework静态库,我们需要的都是二进制文件+.h+其它资源文件的形式,不同的是,.a本身就是二进制文件,需要我们自己配上.h和其它文件才能使用,而.framework本身已经包含了.h和其它文件,可以直接使用。             (2)图片资源的处理:两种静态库,一般都是把图片文件单独的放在一个.bundle文件中,一般.bundle的名字和.a或.framework的名字相同。.bundle文件很好弄,新建一个文件夹,把它改名为.bundle就可以了,右键,显示包内容可以向其中添加图片资源。(3)category是我们实际开发项目中经常用到的,把category打成静态库是没有问题的,但是在用这个静态库的工程中,调用category中的方法时会有找不到该方法的运行时错误(selector not recognized),解决办法是:在使用静态库的工程中配置other linkerflags的值为-ObjC。                                                                                (4)如果一个静态库很复杂,需要暴露的.h比较多的话,就可以在静态库的内部创建一个.h文件(一般这个.h文件的名字和静态库的名字相同),然后把所有需要暴露出来的.h文件都集中放在这个.h文件中,而那些原本需要暴露的.h都不需要再暴露了,只需要把.h暴露出来就可以了。

6.framework动态库的主要作用:

framework本来是苹果专属的内部提供的动态库文件格式,但是自从2014年WWDC之后,开发者也可以自定义创建framework实现动态更新(绕过apple store审核,从服务器发布更新版本)的功能,这与苹果限定的上架的app必须经过apple store的审核制度是冲突的,所以含有自定义的framework的app是无法在商店上架的,但是如果开发的是企业内部应用,就可以考虑尝试使用动态更新技术来将多个独立的app或者功能模块集成在一个app上面!(笔者开发的就是企业内部使用的app,我们将企业官网中的板块开发成4个独立的app,然后将其改造为framework文件集成在一款平台级的app当中进行使用)

7.iOS 利用 framework 进行动态更新!

重要参考文档:

http://blog.csdn.net/like7xiaoben/article/details/44081257

http://www.tuicool.com/articles/Ybq6Rf3

(1)实现思路                                                                                                      将某个app或app中的某个功能模块打包成framework文件集成到一款平台级的app当中来作为入口进行访问。当主程序启动时,从服务器下载framework文件,并放置在主程序的docment文件夹下,需要启动动态库的时候,就调用启动方法给framework传参并加载framework文件,从而实现在主程序中访问动态库的功能!需要更新动态库模块的功能时,从服务器上下载新版本的 framework 并加载即可达到动态更新的目的。Demo代码放在了这里

 (2)常见问题:

NO.1:重点是利用OC的动态特性去实现framework的加载启动!

注意:普通的alloc init的方法无法得到动态库中的类,必须依靠NSClassFromString
和performSelector的动态特性去程序内部查找动态库的类并调用启动方法!

NO.2:当framework 和 主工程中共用相同的第三方库时的问题:

在主工程中启动动态库的时候,打印台会显示ClassXXX is implemented in both XXXandXXX.One of the two will be used.Which one is undefined.

这是当 framework 工程和 主工程链接了相同的第三方库或者同名的类造成的。但是可能并不影响程序的正常运行,一般的做法是在framework 的Build Settings > Other Linker Flags添加-undefined dynamic_lookup标记即可!这样操作的思想就是确保framework也从主工程中链接第三方库进行使用,但是可能会存在一些隐患问题,比如:动态库和主工程中都使用AFNetworking进行网络请求,但是定义的http请求的方法可能不同,如果让动态库调用主工程方法就需要确保主工程中的方法能够满足动态库内部程序的使用,如果打印台出现了上述信息却不影响程序正常运行的时候,建议先不要做过多操作,而是查看动态库启动时选择加载的是哪里的第三方库(经我测试,framework会执行自己的AFNetworking方法而不是主工程中的方法),然后再进行修改。

N0.3:出现“mmap() error 1 at address=0x100704000, size=0x00008000 segment=__TEXT”错误是什么原因?




这个问题主要是因为动态库打包时使用的证书和主程序调试证书不一致造成的,证书不一致时,主程序中是无法打开动态库的!!

NO.4:framework和主工程使用同名的类文件出现的错误:

尝试过在 framework 中引用主工程中已有的文件,通过Build Settings > Header Search Paths中添加相应的目录,Xcode 在编译的时候可以成功(因为添加了-undefined dynamic_lookup),并且 Debug 版本是可以正常运行的,但是 Release 版本动态加载时会提示找不到符号:“Error Domain=NSCocoaErrorDomain Code=3588"The bundle “YourFramework” couldn’t be loaded."(dlopen(/var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework”

这是因为 Debug 版本暴露了所有自定义类的符号以便于调试,因此你的 framework 可以找到相应的符号,而 Release 版本则不会。

目前能想到的方法只有将相同的文件拷贝一份到 framework 工程里,并且更改类名。这一点,只要在平时开发中养成定义类名时添加前缀的习惯基本就能避免该情况出现。

NO.5:怎么访问 framework 中的图片?

framework中使用 storyboard/xib创建的页面,可以直接访问图片!

使用imageNamed方式加载的照片都会丢失!这是因为imageNamed的方法默认是从mainBundle中查找资源的,而framework中的照片存在于主程序的docment文件中,路径错误自然找不到图片资源,所以需要修改加载图片的方法(这是一个比较蛋疼的问题,但是目前只能这么做,我是写了一个UIImage的category方法替换掉framework中所有的imageNamed的方法才实现图片资源的加载)

特别注意:使用代码方式访问的图片不可以放在 xcassets 中,否则得到的将是 nil,这是因为framework中的xcassets文件打包之后变成了.cer文件,并不暴露出图片资源,所以framework无法通过xcassets加载图片资源,解决办法就是建一个文件夹把图片资源都装进去(最好不要在内部设立子文件夹),因为调用图片的时候是通过路径来查找的,子文件过多会增加代码量。图片文件名必须以 @2x/@3x 结尾,大小写敏感。当我们调用图片的时候只使用@符号以前的图片名即可,因为系统会根据手机屏幕的不同自动选择加载该图片名下的@2x或@3x的图片!


NO.6:“dlopen(/path/to/framework,9): no suitable image found.  Didfind:/path/to/framework:mach-o, but wrong architecture”这个错误是说 framework 不支持当前机器的架构。

通过终端命令:lipo -info (把framework中的黑色文件拖进来)可以查看framework的架构信息!我们打包的framework会有四种类型:




如果在模拟器中运行了震级版本或者在真机中运行了模拟器版本都会报上述错误!(打包动态库的方法很简单,如图打包出的就是真机版的动态库,如果选择模拟器,打包出的就是模拟器版本的动态库!)


N0.7签名问题:(参考N0)

系统在加载动态库时,会检查 framework 的签名,签名中必须包含 TeamIdentifier 并且 framework 和 host app 的 TeamIdentifier 必须一致。

如果不一致,否则会报下面的错误:

Error loading/path/to/framework: dlopen(/path/to/framework,265): no suitable image found. Didfind:/path/to/framework:mmap() error1

NO.8:关于other linker flag的设置问题:

使用静态库或者动态库的时候极易发生链接错误,而且大多发生在加载framework中category的情况!根本原因在于Objective-C的链接器并不会为每个方法建立符号表,而是仅仅为类建立了符号表。这样的话,如果静态库中定义了已存在的一个类的分类,链接器就会以为这个类已经存在,不会把分类和核心类的代码合起来。这样的话,在最后的可执行文件中,就会缺少分类里的代码,这样函数调用就失败了。常见的设置方法就是在other linker flag中添加一个语句:-all_load,但是这样也并不是万能的,具体解析请参考链接:http://my.oschina.net/u/728866/blog/194741

注意:当flag里面添加了注释却还是无法使用的时候,可能报flag与bitcode冲突的问题尤其是第三方库可能和bitcode冲突),这样的话就需要将bitcode设置为NO!

bitcode的具体作用不做详谈,可参考:http://www.jianshu.com/p/3e1b4e2d06c6




以下的这些问题是我拷贝原文资料的信息,我在测试中并未遇到相关错误,权作参考:

N0.9:如果用来打包的证书是 iOS 8 发布之前生成的,则打出的包验证的时候会没有 TeamIdentifier 这一项。这时在加载 framework 的时候会报下面的错误:

[deny-mmap]mappedfilehasno team identifierandisnotaplatformbinary:/private/var/mobile/Containers/Bundle/Application/5D8FB2F7-1083-4564-94B2-0CB7DC75C9D1/YourAppNameHere.app/Frameworks/YourFramework.framework/YourFramework

可以通过codesign命令来验证。

codesign -dv /path/to/YourApp.app

如果证书太旧,输出的结果如下:

Executable=/path/to/YourApp.app/YourAppIdentifier=com.company.yourappFormat=bundlewithMach-O thin (armv7)CodeDirectoryv=20100size=221748flags=0x0(none)hashes=11079+5location=embeddedSignaturesize=4321SignedTime=2015年10月21日 上午10:18:37Info.plistentries=42TeamIdentifier=notsetSealed Resourcesversion=2rules=12files=2451Internal requirementscount=1size=188

注意其中的TeamIdentifier=not set。

采用 swift 加载 libswiftCore.dylib 这个动态库的时候也会遇到这个问题,对此Apple 官方的解释是:

To correct this problem, you will need to sign your app using code

signing certificates with the Subject Organizational Unit (OU) set to

your Team ID. All Enterprise and standard iOS developer certificates

that are created after iOS 8 was released have the new Team ID field in

the proper place to allow Swift language apps to run.

If you are an in-house Enterprise developer you will need to be

careful that you do not revoke a distribution certificate that was used

to sign an app any one of your Enterprise employees is still using as

any apps that were signed with that enterprise distribution certificate

will stop working immediately.

只能通过重新生成证书来解决这个问题。但是 revoke 旧的证书会使所有用户已经安装的,用该证书打包的 app 无法运行。

等等,我们就跪在这里了吗?!

现在企业证书的有效期是三年,当证书过期时,其打包的应用就不能运行,那企业应用怎么来更替证书呢?

Apple 为每个账号提供了两个证书,这两个证书可以同时生效,这样在正在使用的证书过期之前,可以使用另外一个证书打包发布,让用户升级到新版本。

也就是说,可以使用另外一个证书来打包应用,并且可以覆盖安装使用旧证书打包的应用。详情可以看Apple 文档