下文是转载,本人以为这个打包framework仍是一个比较重要的功能,能够用来作一下事情:git
(1)封装功能模块,好比有比较成熟的功能模块封装成一个包,而后之后本身或其余同事用起来比较方便。github
(2)封装项目,有时候会遇到这个状况,就是一家公司找了两个开发公司作两个项目,而后要求他们的项目中的一个嵌套进另外一个项目,此时也能够把呗嵌套的项目打包成framework放进去,这样比较方便。bootstrap
咱们为何须要框架(Framework)?app
要想用一种开发者友好的方式共享库是很麻烦的。你不只仅须要包含库自己,还要加入全部的头文件,资源等等。框架
苹果解决这个问题的方式是框架(framework)。基本上,这是含有固定结构并包含了引用该库时所必需的全部东西的文件夹。不幸的是,iOS禁止全部的动态库。同时,苹果也从Xcode中移除了建立静态iOS框架的功能。iphone
Xcode仍然能够支持建立框架的功能,重启这个功能,咱们须要对Xcode作一些小小的改动。单元测试
把代码封装在静态框架是被app store所容许的。尽管形式不一样,本质上它仍然是一种静态库。测试
框架(Framework)的类别优化
大部分框架都是动态连接库的形式。由于只有苹果才能在iOS设备上安装动态库,因此咱们没法建立这种类型的框架。ui
静态连接库和动态库同样,只不过它是在编译时连接二进制代码,所以使用静态库不会有动态库那样的问题(即除了苹果谁也不能在iOS上使用动态库)。
“伪”框架是经过破解Xcode的目标Bundle(使用某些脚本)来实现的。它在表面上以及使用时跟静态框架并没有区别。“伪”框架项目的功能几乎和真实的框架项目没有区别(不是所有)。
“嵌入”框架是静态框架的一个包装,以便Xcode能获取框架内的资源(图片、plist、nib等)。
本次发布包括了建立静态框架和“伪”框架的模板,以及两者的“嵌入”框架。
用哪种模板?
本次发布有两个模板,每一个模板都有“强”“弱”两个类别。你能够选择最适合一种(或者两种都安装上)。
最大的不一样是Xcode不能建立“真”框架,除非你安装静态框架文件xcspec在Xcode中。这真是一个遗憾(这个文件是给项目使用的,而不是框架要用的)。
简单说,你能够这样决定用哪种模板:
若是你不想修改Xcode,那么请使用“伪”框架版本
若是你只是想共享二进制(不是项目),两种均可以
若是你想把框架共享给不想修改Xcode的开发者,使用“伪”框架版本
若是你想把框架共享给修改过Xcode的开发者,使用“真”框架版本
若是你想把框架项目做为另外一个项目的依赖(经过workspace或者子项目的方式),请使用“真”框架(或者“伪”框架,使用-framework——见后)
若是你想在你的框架项目中加入其余静态库/框架,并把它们也连接到最终结果以便不须要单独添加到用户项目中,使用“伪”框架
“伪”框架
“伪”框架是破解的“reloacatable object file”(可重定位格式的目标文件, 保存着代码和数据,适合于和其余的目标文件链接到一块儿,用来建立一个可执行目标文件或者是一个可共享目标文件),它可让Xcode编译出相似框架的东西——其实也是一个bundle。
“伪框架”模板把整个过程分为几个步骤,用某些脚本去产生一个真正的静态框架(基于静态库而不是reloacatable object file)。并且,框架项目仍是把它定义为wrapper.cfbundle类型,一种Xcode中的“二等公民”。
所以它跟“真”静态框架同样能够正常工做,但当存在依赖关系时就有麻烦了。
依赖问题
若是不使用依赖,只是建立普通的项目是没有任何问题的。可是若是使用了项目依赖(好比在workspace中),Xcode就悲剧了。当你点击“Link Binary With Libraries”下方的’+’按钮时,“伪框架”没法显示在列表中。你能够从你的“伪”框架项目的Products下面将它手动拖入,但当你编辑你的主项目时,会出现警告:
warning: skipping file '/somewhere/MyFramework.framework' (unexpectedfile type 'wrapper.cfbundle' in Frameworks & Libraries build phase)
并伴随“伪”框架中的连接错误。
幸运的是,有个办法来解决它。你能够在”Other Linker Flags”中用”-framwork”开关手动告诉linker去使用你的框架进行连接:
-framework MyFramework
警告仍然存在,但起码能正确连接了。
添加其余的库/框架
若是你加入其余静态(不是动态)库/框架到你的“伪”框架项目中,它们将“连接”进你最终的二进制框架文件中。在“真”框架项目中,它们是纯引用,而不是连接。
你能够在项目中仅仅包含头文件而不是静态库/框架自己的方式避免这种状况(以便编译经过)。
“真”框架
“真”框架各个方面都符合“真”的标准。它是真正的静态框架,正如使用苹果在从Xcode中去除的那个功能所建立的同样。
为了能建立真正的静态框架项目,你必需在Xcode中安装一个xcspec文件。
若是你发布一个“真”框架项目(而不是编译),但愿去编译这个框架的人必需也安装xcspec文件(使用本次发布的安装脚本),以便Xcode能理解目标类型。
注意:若是你正在发布彻底编译的框架,而不是框架项目,最终用户并不须要安装任何东西。
我已经提交一个报告给苹果,但愿他们在Xcode中更新这个文件,但那须要一点时间.OpenRadarlink here
加其余静态库/框架
若是你加入其余静态(不是动态)库/框架到你的“真”框架项目,它们只会被引用,而不会象“伪”框架同样被连接到最终的二进制文件中。
从早期版本升级
若是你是从Mk6或者更早的版本升级,同时使用“真”静态框架,而且使用Xcode4.2.1之前的版本,请运行uninstall_legacy.sh以卸载早期用于Xcode的全部修正。而后再运行install.sh,重启Xcode。若是你使用Xcode4.3之后,只须要运行install.sh并重启Xcode。
安装
分别运行Real Framework目录或Fake Framework目录下的install.sh脚本进行安装(或者两个你都运行)。
重启Xcode,你将在新项目向导的Framework&Library下看到StaticiOS Framework(或者Fake Static iOS Framework)。
卸载请运行unistall.sh脚本并重启Xcode。
建立一个iOS框架项目
建立新项目。
项目类型选择Framework&Library下的Static iOS Framework(或者Fake Static iOS Framework)。
选择“包含单元测试”(可选的)。
在target中加入类、资源等。
凡是其余项目要使用的头文件,必需声明为public。进入target的Build Phases页,展开Copy Headers项,把须要public的头文件从Project或Private部分拖拽到Public部分。
编译你的 iOS 框架
选择指定target的scheme
修改scheme的Run配置(可选)。Run配置默认使用Debug,但在准备部署的时候你可能想使用Release。
编译框架(不管目标为iOS device和Simulator都会编译出相同的二进制,所以选谁都无所谓了)。
从Products下选中你的framework,“show in Finder”。
在build目录下有两个文件夹:(yourframework).framework and (your framework).embeddedframework.
若是你的框架只有代码,没有资源(好比图片、脚本、xib、coredata的momd文件等),你能够把(yourframework).framework 分发给你的用户就好了。若是还包含有资源,你必需分发(your framework).embeddedframework给你的用户。
为何须要embedded framework?由于Xcode不会查找静态框架中的资源,若是你分发(your framework).framework, 则框架中的全部资源都不会显示,也不可用。
一个embedded framework只是一个framework以外的附加的包,包括了这个框架的全部资源的符号连接。这样作的目的是让Xcode可以找到这些资源。
使用iOS 框架
iOS框架和常规的Mac OS动态框架差很少,只是它是静态连接的而已。
在你的项目中使用一个框架,只需把它拖仅你的项目中。在包含头文件时,记住使用尖括号而不是双引号括住框架名称。例如,对于框架MyFramework:
#import <MyFramework/MyClass.h>
使用问题
Headers Not Found
若是Xcode找不到框架的头文件,你多是忘记将它们声明为public了。参考“建立一个iOS框架项目”第5步。
No Such Product Type
若是你没有安装iOS Universal Framework在Xcode,并企图编译一个universal框架项目(对于“真”框架,不是“假”框架),这会致使下列错误:
target specifies product type 'com.apple.product-type.framework.static',but there's no such product type for the 'iphonesimulator' platform
为了编译“真”iOS静态框架,Xcode须要作一些改动,所以为了编译“真”静态框架项目,请在全部的开发环境中安装它(对于使用框架的用户不须要,只有要编译框架才须要)。
The selected run 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 momd not 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"];
Unknown class 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 file type 'wrapper.cfbundle' in Frameworks &Libraries build phase
这个问题发生在把“假”框架项目做为workspace的依赖,或者把它看成子项目时(“真”框架项目没有这个问题)。尽管这种框架项目产生了正确的静态框架,但Xcode只能从项目文件中看出这是一个bundle,所以它在检查依赖性时发出一个警告,并在linker阶段跳过它。
你能够手动添加一个命令让linker在连接阶段能正确连接。在依赖你的静态框架的项目的OtherLinker Flags中加入:
-framework MyFramework
警告仍然存在, 但不会致使连接失败。
Libraries being linked or not being linked into the finalframework
很不幸, “真”框架和“假”框架模板在处理引入的静态库/框架的工做方式不一样的。
“真”框架模板采用正常的静态库生成步骤,不会连接其余静态库/框架到最终生产物中。
“假”框架模板采用“欺骗”Xcode的手段,让它认为是在编译一个可重定位格式的目标文件,在连接阶段就如同编译一个可执行文件,把全部的静态代码文件连接到最终生成物中(尽管不会检查是否确实目标代码)。为了实现象“真”框架同样的效果,你能够只包含库/框架的头文件到你的项目中,而不须要包含库/框架自己。
Unrecognized selector 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)
...
在使用这个框架时,仍然还须要在Build Setting的Other Linker Flags中加入-ObjC。
执行任何代码前单元测试崩溃
若是你在Xcode4.3中建立静态框架(或库)target时,勾选了“withunit tests”,当你试图运行单元测试时,它会崩溃:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x0) 0 0x00000000 --- 15 dyldbootstrap:start(...)
这是lldb中的一个bug。你能够用GDB来运行单元测试。编辑scheme,选择Test,在Info标签中将调试器Debugger从LLDB改成GDB。