静态库和动态库详解

   

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

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

2.静态库和动态库有什么特点?

异同点:

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

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

共同点:

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

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

静态库:.a和.framework

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

 

注意:1.两种库都有framework的格式,但是它们长得一样吗?

 

静态库framework
动态库framework

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

 

4. .a文件和.framework文件组成的区别:

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

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

.framework文件内部除了有二进制文件(如下图黑色文件)之外还有其他的资源文件(相当于:.framwork文件=黑色二进制文件<.a文件+.h文件>+资源文件<图片、以及本地的html5,json,plist等),可以直接拿来在工程中使用

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

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

(1)图片资源的处理:两种格式的静态库,一般都是把图片文件单独的放在一个.bundle文件中,一般.bundle的名字和.a或.framework的名字相同。(.bundle文件很好弄,在桌面上新建一个文件夹,把它重命名为XXX.bundle就可以了(选中文件->右键->显示包内容->拖拽添加图片资源))。

(2)category是我们实际开发项目中经常用到的,把category打成静态库是没有问题的,但是在使用这个静态库的工程中,调用category中的方法时,会出现找不到该方法的运行时错误:selector not recognized,解决办法是:在使用静态库的工程中配置other linkerflags的值为-ObjC。                                                                               

(3)如果一个静态库很复杂,需要暴露的.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当中进行使用,这样就可以在一款app上面使用原本4个app的全部功能!)

7.iOS 如何使用 framework 来进行动态更新!

重要参考文档(一定要看):

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

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

 (1)常见问题汇总:

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创建的页面,可以直接访问framework中图片资源!

但是framework中通过"imageNamed:"方式加载的照片都会丢失!!!这是因为imageNamed的方法默认是从mainBundle中查找资源的,而framework中的照片是从framework内部加载的,这是的bundle并不是mainBudle,而是存在于主程序的docment文件中的framework包,图片加载的路径发生了变化,自然找不到图片资源,所以需要修改加载图片的方法!(这是一个很蛋疼的问题,意味着原先工程中加载图片的imageNamed的方法都需要更改为从指定bundle加载图片资源的方法。我是在framework中写了一个UIImage的category方法"imageFromBundleNamed"替换掉framework中所有的“imageNamed”的方法才实现图片资源的加载)

NO.6主工程中加载framework可能出现弹窗显示位置失常!

这个问题是很容易出现的,因为在主工程中打开framework就向从一款App跳转到另一款App,framwork中弹出的窗口并不受主工程的navigationController的管理,这样就极可能导致弹窗展示位置出错。

解决办法是:1.将framework中弹窗加载的keyWindow更换为lastWindow(keyWindow是当前活跃的window,而lastWindow是最顶部的window);2.在framwork中通过navigationController.view获取弹窗展示的当前View(此时的View也必然是framework中最前端的页面),然后做相应操作!

NO.7在framework中,图片资源不能使用xcassets进行管理!

这是因为framework中的xcassets文件打包之后会变成.cer文件,并不暴露出图片资源,所以framework不能通过xcassets来管理图片资源。

解决办法是:创建一个资源文件夹把全部图片文件都装进去进行管理。

NO.8图片命名问题:

同一个图片包含多种适配尺寸的,必须确保图片名一致,且以 @2x/@3x 结尾,大小写敏感。当我们调用图片的时候只使用@符号以前的图片名即可,因为系统会根据手机屏幕的不同自动选择加载该图片名下的@2x或@3x的图片!

 

NO.9架构错误:

错误描述:“dlopen(/path/to/framework,9): no suitable image found.  Didfind:/path/to/framework:mach-o, but wrong architecture”

解决办法:打包framework的时候一定要分清是支持iphone还是iphonesimulayor,如果打包类型有误就不能在相应设备上正常启动framework文件;当然,最好是通过lipo的合并命令讲真机包和模拟器包合并一下,这样就不用担心引起架构错误的问题!

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

黑色文件才是要合并的文件

(2)合并命令:lipo -create   xxx  xxx -output newName

“xxx”是:将模拟器包和真机包的黑色文件拖拽到终端中时产生的路径信息;

“newName”是合并后自定义的名字,不过我一般是再拖一遍真机包的路径来替换这个名字,这样合并后的文件就直接包含在真机包里面,然后拿来使用。

N0.10签名问题:

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

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

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

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

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

关于flag标注信息:-all load和-Objc的区别请参考链接:http://my.oschina.net/u/728866/blog/194741

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

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

 


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

N0.12:如果用来打包的证书是 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 文档

posted @ 2016-08-09 10:15  Dennis丶SN  阅读(9392)  评论(1编辑  收藏  举报