动态库和静态库的区别
库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行。库分静态库和动态库两种。
1. 静态函数库
这类库的名字一般是libxxx.a;利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。
2. 动态函数库
相对于静态函数库,动态函数库在编译的时候 并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。
静态库和动态库都是由*.o目标文件生成的,这是他们的相同之处。
n ios开发中,动态库与静态库使用上的区别
在ios开发中,动态库的名字一般是libxxx.dylib,静态库的名字一般是libxxx.a(xxx.framework是静态库文件.a的一层封装,后续介绍)。
使用静态库的好处
1,模块化,分工合作
2,避免少量改动经常导致大量的重复编译连接
3,也可以重用,注意不是共享使用
动态库使用有如下好处:
1、可以将最终可执行文件体积缩小
2、多个应用程序共享内存中得同一份库文件,节省资源
3、可以不重新编译连接可执行程序的前提下,更新动态库文件达到更新应用程序的目的。
从1可以得出,将整个应用程序分模块,团队合作,进行分工,影响比较小等其他好处,
从2可以看出,其实动态库应该叫共享库,那么从这个意义上来说,苹果禁止iOS开发中使用动态库就可以理解了:因为在现在的iPhone,iPodTouch,iPad上面程序都是单进程的,也就是某一时刻只有一个进程在运行,那么你写个共享库,
----共享给谁?(你使用的时候只有你一个应用程序存在,其他的应该被挂起了,即便是可以同时多个进程运行,别人能使用你的共享库里的东西吗?你这个是给你自己的程序定制的。)
----目前苹果的AppStore不支持模块更新,无法更新某个单独文件(除非自己写一个更新机制:有自己的服务端放置最新动态库文件)
至于苹果为什么禁止ios开发使用动态库我就猜到上面俩原因。
iPhone官方只支持静态库联编。
由于ios开发,官方只支持静态库联编,所以在实际的开发中,系那个要实现模块化分工合作,只能使用静态库了。
n 深入理解iPhone静态库
在实际的编程过程中,通常会把一些公用函数制成函数库,供其它程序使用,一则提搞了代码的复用;二则提搞了核心技术的保密程度。所以在实际的项目开发中,经常会使用到函数库,函数库分为静态库和动态库两种。和多数人所熟悉的动态语言和静态语言一样,这里的所谓静态和动态是相对编译期和运行期的:静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要改静态库;而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在。
对于静态库的使用,可以有libxxx.a和xxx.framework(framework是静态库的封装,暂且这么理解),他们的区别如下:
1、 Static libraries(xxx.a) are fine, butthey require a bit of extra work on the part of the user. You need to link yourproject to the library and you need to copy the header files into your projectand reference all the header filessomewhere by setting the appropriate header search paths in your build settings.
2、 The biggest advantage a framework hasover static libraries is that they act as a neat way of packaging up thecompiled library binary and any related headers(以最优越的方式打包编译的二进制库和相关的头文件).
They can be dropped into your project (just like theSDK's built-in frameworks like Foundation and UIKit) and they should just work(most of the time).
n 为什么要选择framework?
动态库不可用,静态链接库.a用起来的颇为麻烦,libxml2就是一个例子,首先要加入linker内让linker找到符号,然后还需要配置Header的搜索路径让compiler可以找到这些符号的定义。基于这种不人性化操作,苹果在自己的平台上推出了一种新的引用方式,那就是Framework。
Framework其实就是将Header及Library打包在一起方便使用,在苹果的官方文档,Framework还可以嵌套打包,其称为Umbrella Framework,不过其不建议普通用户这样做而已。我们平常使用的QuartzCore、UIKit、CoreFoundation等均打包为Framework。
n 如何创建framework?
因为xcode没有对应生成framework模板,所以不能简单地创建一个Framework,但是一个开源的模板项目可以帮到我们:iOS-Universal-Framework,这个开源的模板提供了FakeFramework(伪框架)和RealFramework(真框架),具体区别如下:
看到名字开始会很疑惑,这东西还能有Fake和Real的?
Fake的是通过一个bundle模拟Framework,这个不需要对Xcode进行任何改动,只需要添加一个项目模板即可,但是使用这个模板并且开启ARC时候会出现问题,这个后面会说道。
Real的生成出来的product就是一个真真正正的Framework,但是由于Xcode不支持在iOS上使用自定义Framework,所以需要向Xcode内加入两个xcspec文件以启用支持。
将两个模板都安装完成后,可以看见iOS中的“Framework & Library”多出了两个Framework的模板。看看名字,里面均有一个“Static”,这表示生成的Framework都是静态的,因为苹果是禁止开发者向iOS内添加动态库的,这样就可以保证所有应用的依赖均来源于苹果内部,降低开发难度也可以保证应用不会因为缺少依赖库而Crash。所以,无论Static Framework或者StaticLibrary,最终所有的符号均会被内联到程序中,跟直接在项目内编写代码是一样的。
既然生成的二进制都是一样的,为什么还要提取一个库出来呢?虽然iOS上二进制是不可共享的,但是代码是可以共享的,可以将经常使用的组件提出成一个公共库,再供给程序使用,这样可以有效减低程序内的代码量。(说离题了。。。)
下面说说Static Library、Fake Static Framework、RealStatic Framework 使用需要注意的事项以及其优劣,特别是在DeploymentTarget 在 iOS 5.0以下时候容易出现的问题。
Static Library:
简单的静态链接库,其使用Libtool生成静态链接库文件,不会因为开启iOS 4.0以下版本的ARC而强制将libarclite链接至静态链接库;但是,其使用较为麻烦,不方便发布。
Static Library在生成的静态库时候会加上一个参数-arch_onlyarmv7(使用模拟器时是:-arch_only i386),即使这个库只可用于模拟器或者真实设备,现在貌似暂时没有办法设置其同时支持armv7和i386。
Fake Static Framework:
这个不是一个真正的Framework,实际类型是CoreFoundation Bundle,所以叫做FakeFramework。
因为其生成的目标是CFBundle,所以Xcode使用Ld命令生成目标静态库,这样在工程内的Link Binary with Libraries不能有系统的Framework和Library,一旦引入系统的库,Ld命令会将引入库的所有符号内联到当前工程中,当其它工程引用这个静态库的时候就会出现Duplicate symbol define的错误!
_基于这个原因,在Deployment Target低于5.0而且开启ARC的时候可能会出现Duplicatesymbol define: objc_retainObject()的错误,出现这个错误的原因如下:
1. Fake Static Framework使用Ld命令生成库,而不是使用Libtool生成库,当Framework工程的DeploymentTarget低于5.0而且开启ARC的时候编译器会向程序写入内存管理方面的代码,而Ld命令会将libarclite库内对应的函数(如objc_retainObject())的定义加入到当前的静态库中。
2. 在引用Framework的工程中,如果DeploymentTarget低于5.0而且开启ARC,同1道理,Ld命令会向项目内引入libarclite库并将定义加入当前项目,但是由于在引入的Framework中已经包含有相关符号,所以会出现Duplicate symboldefine。
解决方案如下:在FakeStatic Framework生成的时候将Deployment Target改为5.0或更高,在编写代码的时候将其改回对应版本(因为5.0以下的不支持weak关键字,改回低版本有利于IDE的提示);或者将引用Framework的工程的DeploymentTarget改为5.0或更高,但是这样则会失去对低版本的支持。
Real Static Framework:
这个是一个真正的Framework,其生成的文件实际类型就是Framework。其内部包含的静态库是由Libtool生成的,所以不存在ARC的问题,而且可以在LinkBinary with Libraries中加入系统的Framework,其只会在生成的库中加入对应Framework或Library的引用,而不会将定义也加入其中。
但是,由于苹果的限制,Xcode不支持RealStatic Framework直接引用,需要加入两个xcspec文件才可以使用,但是使用这个方式的Framework会将不会存在上述两个静态库的n问题,是现时来说最方便的库。
n 创建一个iOS框架项目
分别运行Real Framework目录或Fake Framework目录下的install.sh脚本进行安装(或者两个你都运行)。
重启Xcode,你将在新项目向导的Framework&Library下看到StaticiOS Framework(或者Fake Static iOS Framework)。
卸载请运行unistall.sh脚本并重启Xcode。
创建一个iOS框架项目
1. 创建新项目。
2. 项目类型选择Framework&Library下的Static iOS Framework(或者Fake Static iOS Framework)。
3. 选择“包含单元测试”(可选的)。
4. 在target中加入类、资源等。
5. 凡是其他项目要使用的头文件,必需声明为public。进入target的Build Phases页,展开Copy Headers项,把需要public的头文件从Project或Private部分拖拽到Public部分。
编译你的 iOS 框架
1. 选择指定target的scheme
2. 修改scheme的Run配置(可选)。Run配置默认使用Debug,但在准备部署的时候你可能想使用Release。
3. 编译框架(无论目标为iOS device和Simulator都会编译出相同的二进制,因此选谁都无所谓了)。
4. 从Products下选中你的framework,“show in Finder”。
在build目录下有两个文件夹:(yourframework).framework and (yourframework).embeddedframework.
如果你的框架只有代码,没有资源(比如图片、脚本、xib、coredata的momd文件等),你可以把(yourframework).framework 分发给你的用户就行了。如果还包含有资源,你必需分发(yourframework).embeddedframework给你的用户。
为什么需要embedded framework?因为Xcode不会查找静态框架中的资源,如果你分发(yourframework).framework, 则框架中的所有资源都不会显示,也不可用。
一个embedded framework只是一个framework之外的附加的包,包括了这个框架的所有资源的符号链接。这样做的目的是让Xcode能够找到这些资源。
使用iOS 框架
iOS框架和常规的Mac OS动态框架差不多,只是它是静态链接的而已。
在你的项目中使用一个框架,只需把它拖仅你的项目中。在包含头文件时,记住使用尖括号而不是双引号括住框架名称。例如,对于框架MyFramework:
#import
n 使用编译好的框架碰到的若干问题
Headers NotFound
如果Xcode找不到框架的头文件,你可能是忘记将它们声明为public了。参考“创建一个iOS框架项目”第5步。
No Such ProductType
如果你没有安装iOS Universal Framework在Xcode,并企图编译一个universal框架项目(对于“真”框架,不是“假”框架),这会导致下列错误:
targetspecifies product type 'com.apple.product-type.framework.static',but there's nosuch product type for the 'iphonesimulator' platform
为了编译“真”iOS静态框架,Xcode需要做一些改动,因此为了编译“真”静态框架项目,请在所有的开发环境中安装它(对于使用框架的用户不需要,只有要编译框架才需要)。
The selectedrun destination is not valid for this action
有时,Xcode出错并加载了错误的active设置。首先,请尝试重启Xcode。如果错误继续存在,Xcode产生了一个坏的项目(因为Xcode4的一个bug,任何类型的项目都会出现这个问题)。如果是这样,你需要创建一个新项目重来一遍。
链接警告
第一次编译框架target时,Xcdoe会在链接阶段报告找不到文件夹:
ld: warning:directory not found for option'-L/Users/myself/Library/Developer/Xcode/DerivedData/MyFramework-ccahfoccjqiognaqraesrxdyqcne/Build/Products/Debug-iphoneos'
此时,可以clean并重新编译target,警告会消除。
Core Data momdnot found
对于框架项目和应用程序项目,Xcode会以不同的方式编译momd(托管对象模型文件)。Xcode会简单地在根目录创建.mom文件,而不会创建一个.momd目录(目录中包含VersionInfo.plist和.mom文件)。
这意味着,当从一个embedded framework的model中实例化NSManagedObjectModel时,你必需使用.mom扩展名作为model的URL,而不是采用.momd扩展名。
NSURL *modelURL= [[NSBundle mainBundle]URLForResource:@"MyModel"withExtension:@"mom"];
Unknownclass MyClass in Interface Builder file.
由于静态框架采用静态链接,linker会剔除所有它认为无用的代码。不幸的是,linker不会检查xib文件,因此如果类是在xib中引用,而没有在O-C代码中引用,linker将从最终的可执行文件中删除类。这是linker的问题,不是框架的问题(当你编译一个静态库时也会发生这个问题)。苹果内置框架不会发生这个问题,因为他们是运行时动态加载的,存在于iOS设备固件中的动态库是不可能被删除的。
有两个解决的办法:
让框架的最终用户关闭linker的优化选项,通过在他们的项目的Other Linker Flags中添加-ObjC和-all_load。
在框架的另一个类中加一个该类的代码引用。例如,假设你有个MyTextField类,被linker剔除了。假设你还有一个MyViewController,它在xib中使用了MyTextField,MyViewController并没有被剔除。你应该这样做:
在MyTextField中:
+(void)forceLinkerLoad_ {}
在MyViewController中:
+(void)initialize { [MyTextField forceLinkerLoad_]; }
他们仍然需要添加-ObjC到linker设置,但不需要强制all_load了。
第2种方法需要你多做一点工作,但却让最终用户避免在使用你的框架时关闭linker优化(关闭linker优化会导致object文件膨胀)。
unexpected filetype 'wrapper.cfbundle' in Frameworks &Libraries build phase
这个问题发生在把“假”框架项目作为workspace的依赖,或者把它当作子项目时(“真”框架项目没有这个问题)。尽管这种框架项目产生了正确的静态框架,但Xcode只能从项目文件中看出这是一个bundle,因此它在检查依赖性时发出一个警告,并在linker阶段跳过它。
你可以手动添加一个命令让linker在链接阶段能正确链接。在依赖你的静态框架的项目的OtherLinkerFlags中加入:
-frameworkMyFramework
警告仍然存在, 但不会导致链接失败。
Libraries beinglinked or not being linked into the finalframework
很不幸, “真”框架和“假”框架模板在处理引入的静态库/框架的工作方式不同的。
“真”框架模板采用正常的静态库生成步骤,不会链接其他静态库/框架到最终生产物中。
“假”框架模板采用“欺骗”Xcode的手段,让它认为是在编译一个可重定位格式的目标文件,在链接阶段就如同编译一个可执行文件,把所有的静态代码文件链接到最终生成物中(尽管不会检查是否确实目标代码)。为了实现象“真”框架一样的效果,你可以只包含库/框架的头文件到你的项目中,而不需要包含库/框架本身。
Unrecognizedselector in (some class with a category method)
如果你的静态库或静态框架包含了一个模块(只在类别代码中声明,没有类实现),linker会搞不清楚,并把代码从二进制文件中剔除。因为在最终生成的文件中没有这个方法,所以当调用这个类别中定义的方法时,会报一个“unrecognizedselector”异常。
要解决这个,在包含这个类别的模块代码中加一个“假的”类。linker发现存在完整的O-C类,会将类别代码链接到模块。
我写了一个头文件 LoadableCategory.h,以减轻这个工作量:
#import"SomeConcreteClass+MyAdditions.h"
#import"LoadableCategory.h" MAKE_CATEGORIES_LOADABLE(SomeConcreteClass_MyAdditions); @implementation SomeConcreteClass(MyAdditions)
...
@end
在使用这个框架时,仍然还需要在Build Setting的Other Linker Flags中加入-ObjC。
执行任何代码前单元测试崩溃
如果你在Xcode4.3中创建静态框架(或库)target时,勾选了“withunit tests”,当你试图运行单元测试时,它会崩溃:
Thread 1:EXC_BAD_ACCESS (code=2, address=0x0) 0 0x00000000 --- 15dyldbootstrap:start(...)
这是lldb中的一个bug。你可以用GDB来运行单元测试。编辑scheme,选择Test,在Info标签中将调试器Debugger从LLDB改为GDB。