最近在要把写的代码打包成Framework包给其他人用,照着网上的博客介绍的过程做出来以后,发现在64位上运行时会崩溃掉,数个小时的google和测试后,最终在github上找到了一位大神写的文章,他自己写了一个脚本,将该问题解决掉了。(32个赞!)
下面是对他文章的翻译,注释是我在制作的工程中发现要注意的地方,原文地址:https://github.com/csexton/ios-framework-builder/blob/master/HOWTO.md
我的灵感极大程度上来自于Jeff VerKoeyen的iOS Framework指南,然而当苹果发布了新的64位架构后,打破了原有的一些事情。主要是我们现在有32位和64位的模拟器架构。不幸地是xcode并没有给你任何的方式来指定你想给模拟器设置的架构,当我在模拟器里编译时,我无法弄清楚如何巧妙地编译一个臃肿的二进制包。我可以用xcodebuild和一些环境变量来解决这个问题。我还希望把所有的框架编译工作放在一起,而不是对不同的target进行不同的编译设置。
在这篇文章的末尾,你将得到一个单独的aggregate target,它会构建你的二进制包,并把它编译成.framework。原始的target通常只是构建了一个.a文件。
1 制作一个通用的Cocoa Touch Static Library
1.1 创建一个新的工程
如果有必要,那就创建一个新的library工程。
这只会生成一个.a二进制包和hearders文件。我们将添加一个target,这个target将把它转换成一个合适的framework
1.2 Framework Header
你需要创建一个这个framework总的要导入的头文件。这个会提供给你的library的使用者,而不是这个library的本身。
例如我已经有一个叫作"MyFramework"的framework包,还有一个MyFramework/MyFramework头,像下面这样:
#import <Foundation/Foundation.h>
#import <MyFramework/MyFramework.h>
我只要将该头文件添加到工程中即可使用MyFramework包中的功能了。
1.3 公开的文件
首先你需要添加一个Copy Header build phase。到状态栏,然后点击:
- Editor
- Add Build Phase
- Add Copy Headers Build Phase
这时你看的界面如下所示:
为了暴露你插件包中的功能,你需要公开对外可见的头文件。用XCode的"Target Membership"来完成。
为了设置membership mash ⌘-⌥-0,然后勾选或不勾选Target Membership,然后设置为"project"
注:当你要把一个头文件放到.framework里时,你就要这样做。
(注:在我测试的过程中发现,要把那一项设置为public的才行,是project的话对外还是不可见的)
更新公开头文件的路径。在build setting中搜索"Public Headers Folder Path",然后将其值改变为:
$(PROJECT_NAME)Headers
这会把头文件放到一个像MyProjectHeader中的文件夹中,它会被复制到framework包中。
1.4 禁用Code Striping
点击工程名,build settings,选择"combined",然后更新下面的设置:
- Dead Code Stripping: No
- Strip Debug Symbols During Copy: No
- Strip Style: Non-Global Symbols
2 Framework Target
创建一个新的aggregate target
从菜单栏中添加一个新的target:
- File
- New
- Target
- Other
- Aggregate
Add Dependencies
构建Framework
- Editor
- Add Build Phase
- Add Run Script Build Phase
只要粘贴一个命令到你的脚本中,如果有必要再调整路径。
set -e
${PROJECT_DIR}/${PROJECT_NAME}/Scripts/framework-builder
这是真正神奇的地方,这个脚本处理了所有关键的事情。把脚本内容 放到一个叫Scripts/framework-builder的文件中,然后添加到工程中。
关于这个脚本有几点要解释一下:
- 我使用的ruby,很大程度是因为它在bash中处理的大量复杂的边界情况。然后这可以使用包括中Mac OS X系统中的ruby命令,根本无需依赖包。它应该会起作用。他设置避免了任何可能被安装的ruby版本的管理。
- 这会重新编译所有target来确保满足所有包括进来的架构。这是一个有点笨拙的解决方案,但是却很管用。
- 假定$PROJECT\_NAME与你的library和framework的名字一样。如果不是就编辑一下这个脚本。
curl https://raw.github.com/csexton/ios-framework-builder/master/standalone/framework-builder > Scripts/framework-builder
如果你对这个处理过程有任何更改,请随意发送pull request,我相信苹果会有一天对其进行改善的。
制作资源Bundle包
NSBundle *bundle = [NSBundle bundleWithURL:[[NSBundle mainBundle] URLForResource:@"MyFrameworkResources" withExtension:@"bundle"]];
MyViewController *controller = [[MyViewController alloc]initWithNibName:@"MyViewController" bundle:bundle];
但是请注意: 在IB中对xib上的控件设置的图片并不会收到影响,不用加上这一层路径
- Resource.bundle一定要放在使用这个资源包的工程下面,否则会找不到资源文件。
- 我刚才说的是把写好的代码,打包出来一个framework包和bundle包,你在做时最好是直接建一个cocoa touch static library工程,然后再建一个测试的工程,以免文件过多时再打包不好弄。在测试工程中可以直接引用生成的framework,这样在编译完framework后就不用再替换一次测试工程中的framework包了。
- 如果在framework中用到了category,那么在使用该framework包时要在工程的Build Settings中Linking下的Other Linker Flags设置为-all_load。
- 尽量在源代码所在的那个目录中只放.m和.h文件,因为这个目录中的所有文件都会被放到framework包中,这样徒增了framework包的大小。
- 在新建完Cocoa Touch Static Library后,你会发现在Frameworks的组下面少了UIKit.framework,如果你用到了这里面的东西,那在Build Phases中将其添加进来就好。
@interface NSBundle (MyLibrary)
+ (NSBundle*)myLibraryResourcesBundle;
@end
@implementation NSBundle (MyLibrary)
+ (NSBundle*)myLibraryResourcesBundle {
static dispatch_once_t onceToken;
static NSBundle *myLibraryResourcesBundle = nil;
dispatch_once(&onceToken, ^{
myLibraryResourcesBundle = [NSBundle bundleWithURL:[[NSBundle mainBundle] URLForResource:@"MyLibraryResources" withExtension:@"bundle"]];
});
return myLibraryResourcesBundle;
}
@end
@implementation UIImage (MyLibrary)
+ (UIImage*)myLibraryImageNamed:(NSString*)name;
@end
@implementation UIImage (MyLibrary)
+ (UIImage*)myLibraryImageNamed:(NSString*)name {
UIImage *imageFromMainBundle = [UIImage imageNamed:name];
if (imageFromMainBundle) {
return imageFromMainBundle;
}
UIImage *imageFromMyLibraryBundle = [UIImage imageWithContentsOfFile:[[[NSBundle myLibraryResourcesBundle] resourcePath] stringByAppendingPathComponent:name]];
return imageFromMyLibraryBundle;
}
@end