Swift构建通用版本Framework以及Framework的使用及其注意事项

前段时间公司app中有个BookRoom模块,也就是绘本阅读的模块需要重新做,并且封装成framework的形式进行(fcs项目已经很大,很冗余,每次Xcode编译项目都需要大量的时间,至少有5分钟),所以使用swift构建的framework遇到的问题坑我基本上都遇到过。于是想把整个过程记录下来,肯定有人和我一样遇到类似的问题,以便下次遇到可以快速解决;

一、新建Framework项目

  1. 新建项目选择Cocoa Touch Framework项目,取名为BookRoomKit,选择Swift语言


    新建framework

    bookroomkit
  2. 然后新建一个BookRoomManager单例类用来传值设置,并且需要申明为public,这样我们才能够在其他项目上使用我们提供的framwork的代码;

  3. 编译framework;我这里以真机发布版本framework的为例。
    首先使用Product->Scheme->Edit Scheme 打开如下界面,设置为Release环境,

    设置编译环境为release

    然后按照下图选择,第1步代表真机运行,第2步运行,第3步代表编译成功的BookRoomKit.framework;
    编译framework

    选中BookRoomKit.framework,右击Show inFinder,会显示编译好的framework在finder中的位置,如下图:
    show in finder

  4. 接下来我们新建一个壳工程,也就是测试工程,用来使用BookRoomKit.framework;并且命名为BookRoomDemo,如下图:

    BookRoomDemo App

    BookRoomDemo

    将BookRoomKit.framework拖入项目中,勾选下面三项,然后点击Finish
    勾选的三项

    编写代码前,记得将壳工程切换为release环境,然后真机运行,因为编译的framework必须与壳工程运行的环境一致才能够运行成功。
    编写测试代码

    运行之后你会发现,程序运行不起来,奔溃的错误信息是:

dyld: Library not loaded: @rpath/BookRoomKit.framework/BookRoomKit
  Referenced from: /var/containers/Bundle/Application/C3208286-60C0-456D-B512-C26FB1E6A254/BookRoomDemo.app/BookRoomDemo
  Reason: image not found

奔溃信息日志

这里就涉及一个问题,那就是我之前的一步操作是直接从Finder中将framework拖拽进壳工程项目的,这样操作默认会将framework放置在Linked Frameworks and Libraries下面,如下图;

第一:Embedded Binaries和Linked Frameworks and Libraries是有区别,具体区别看这篇文章What is the difference between Embedded Binaries and Linked Frameworks;第三:从ios8开始,苹果官方支持我们构建的dynamic framework,我们构建的BookRoomKit.framework就是dynamic frameworks,这种类型的framework它需要签名code-signed以及嵌入到我们的app,否则我们真机运行时就会奔溃Embedded Binaries with iOS Framework,还有一个问题就是,我们构建的BooRoomKit.framework往往需要使用第三方的网络请求框架,JSON解析框架等等,这些都因为代码签名的问题都得在壳工程中添加
说了这么多,解决方案就是:选中Linked Frameworks and Libraries下面的BookRoomKit.framework点击删除,然后将左侧导航栏的framewotk拖拽到Embedded Binaries下面,这时Linked Frameworks and Libraries默认也会有,如下图:
正确的方法

再次运行,你会发现运行成功,而且打印了hello bookroom
hello bookroom

二、Framework合并

  1. 使用lipo -info查看framework支持的cpu架构,分别对应真机版本和模拟器版本的framework信息
    lipo -info
  2. 使用lipo -create指令将模拟器和真机的framework合并成通用版本
    真机与模拟器的framework

    具体操作如下:首先,切换到/BookRoomKit/Build/Products;然后执行lipo -create -output [name] [path1] [path2]这条命令;执行完成后在Products目录下生成一个BookRoomKit文件。
    lipo -create1

    lipo -create2

    然后将上图红圈的文件复制到Release-iphoneos中去覆盖原来的版本,最后将Release-iphonesimulator中的框架文件里的/Modules/BookRoomKit.swiftmodule里的文件复制到Release-iphoneos对应的文件夹下。这样我们就得到了一个通用的的框架。
    通用的framework

    最后用这个framework替换掉测试工程的framewotk就可以在真机和模拟器运行了

三、测试framework专用

为了便于测试工程使用framework,我这里教大家把两个项目放到一个Xcode里面打开,也不用Xcode打开两个项目,这样便于测试使用,也不用来回在framework工程和壳工程之间切换。操作如下:关闭BookRoomKit项目,打开BookRoomDemo项目,将之前的操作取消回退,删除集成进来的Book Room.framework。然后将BookRoomKit.xcodeproj拖拽进BookRoomDemo项目中,编译BookRoomKit,如下图:

使用一个项目打开

然后将编译好的framework拖拽进BookRoomDemo中的Embedded Binaries,如下图
3F6F9E91-A8CB-4ACE-B0B4-D21ABD9C5BA8.png

通过上面的操作你就可以方便的测试framework编写的代码,不用每次都编译构建framework,当你运行BookRoomDemo的时候你就能够使用framework暴露出来的方法调用。

四、Framework中使用image,xib,storyboard,font等资源文件

  1. image, xib,storyboard都是需要传递相应的bundle。而我看到网上一些旧的教程都很繁琐,还要建立什么resources.bundle之类的。经过我的实践,我这种方法更为简单。
let bundle = Bundle(identifier: "xcqromance.BookRoomKit") // framework的bundle ID

storyboard的加载,xib类似

 let sb = UIStoryboard(name: "BookHome", bundle: bundle)
 let vc = sb.instantiateViewController(withIdentifier: "BookViewController") as! BookViewController
 viewController.navigationController?.pushViewController(vc, animated: true)

image的加载

let image = UIImage(named: "bookroom_down_bg_blue", in: bundle, compatibleWith: nil)
let imageView = UIImageView(image: image)
  1. font的加载则是比较麻烦,得先注册,才能使用!所以我写了个OC的NSObject分类
+ (UIFont *) loadMyCustomFont:(NSString *)name size: (CGFloat)size type: (NSString *)type {
  NSString *fontPath = [[NSBundle bundleWithIdentifier:@"xcqromance.BookRoomKit"] pathForResource:name ofType:type];
  NSData *inData = [NSData dataWithContentsOfFile:fontPath];
  CFErrorRef error;
  CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)inData);
  CGFontRef font = CGFontCreateWithDataProvider(provider);
  if (! CTFontManagerRegisterGraphicsFont(font, &error)) {
    CFStringRef errorDescription = CFErrorCopyDescription(error);
    NSLog(@"Failed to load font: %@,%@", errorDescription,name);
    CFRelease(errorDescription);
  }
  CFRelease(font);
  CFRelease(provider);
  NSString *fontName = (__bridge NSString *)CGFontCopyPostScriptName(font);
  UIFont* uifont = [UIFont fontWithName:fontName size:size];
  return uifont;
}

然后在单例模初始化的时候进行注册。使用这个字体的方法和平时的一样,将fontName传递下就可以了

let label = UILabel()
label.text = "hello word"
label.sizeToFit()
label.center = view.center
label.font = UIFont(name: "Kreon-Bold", size: 17)

Github上面下载BookRoomDemo

---------------------2017.04.10更新---------------------
每次使用终端lipo -create创建通用版本很繁琐、低效,于是想到了将这个过程脚本化;

使用脚本一键构建通用版本的framework(真机、模拟器通吃的版本)

步骤如下:

  1. build active architecture only设置为No
    build active architecture only -> No
  2. 新建一个target,用来构建通用版本framework


    new target

    选择Cross-platform->other->Aggregate->Next


    Aggregate

    命名为univeralBuilder,新建一个New Run Script Phase
    D124D6A4-B28B-4632-95F2-568278624B06.png

    在shell里面添加以下内容,注意将第九行的FRAMEWORK_NAME改为自己framework的名字。

# Merge Script

# 1
# Set bash script to exit immediately if any commands fail.
set -e

# 2
# Setup some constants for use later on.
FRAMEWORK_NAME="Your framework name" 

# 3
# If remnants from a previous build exist, delete them.
if [ -d "${SRCROOT}/build" ]; then
rm -rf "${SRCROOT}/build"
fi

# 4
# Build the framework for device and for simulator (using
# all needed architectures).
xcodebuild -target "${FRAMEWORK_NAME}" -configuration Release -arch arm64 -arch armv7 -arch armv7s only_active_arch=no defines_module=yes -sdk "iphoneos"
xcodebuild -target "${FRAMEWORK_NAME}" -configuration Release -arch x86_64 -arch i386 only_active_arch=no defines_module=yes -sdk "iphonesimulator"

# 5
# Remove .framework file if exists on Desktop from previous run.
if [ -d "${HOME}/Desktop/${FRAMEWORK_NAME}.framework" ]; then
rm -rf "${HOME}/Desktop/${FRAMEWORK_NAME}.framework"
fi

# 6
# Copy the device version of framework to Desktop.
cp -r "${SRCROOT}/build/Release-iphoneos/${FRAMEWORK_NAME}.framework" "${HOME}/Desktop/${FRAMEWORK_NAME}.framework"

# 7
# Replace the framework executable within the framework with
# a new version created by merging the device and simulator
# frameworks' executables with lipo.
lipo -create -output "${HOME}/Desktop/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${SRCROOT}/build/Release-iphoneos/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${SRCROOT}/build/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}"

# 8
# Copy the Swift module mappings for the simulator into the
# framework.  The device mappings already exist from step 6.
cp -r "${SRCROOT}/build/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule/" "${HOME}/Desktop/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule"

# 9
# Delete the most recent build.
if [ -d "${SRCROOT}/build" ]; then
rm -rf "${SRCROOT}/build"
fi

3.最后一步:选择Aggregate target和Simulator然后build,你会看到桌面由构建好通用版本的framework。
参考资料:http://arsenkin.com/ios-universal-framework.html

2017.11.07添加

五、Framework依赖第三方库

有人在下面提问,如果我新建的framework还依赖第三方库怎么解决?而这也是一个很正常的需求。比如我的BookRoomKit.framework有一个绘本zip包解压的功能,此时我有两种解决方案,第一将ZipArchive的代码拖入到我的framework中,还有一种是ZipArchive.framework引入到我的framework中。明显我们会选择第二种方式,一旦有更新直接替换新的的framework就行。而第二种方式又有两种方法引入:第一:在壳工程中使用pods、carhtage第三方库管理工具来添加;第二:直接将依赖的framework拖入壳工程。其实这两种方式的本质都是一样的,都有一个很关键的点就是Framework 的Build Settings中设置好依赖的第三方库的Framework Search Paths,同时在Build Phases的Link Binary With Binaries添加依赖的第三方库,下面我就以第二种方法直接将依赖的第三方库拖入壳工程进行配置为例。

  1. 将下载好的ZipArchive.framework拖入壳工程,记得勾选Copy items if needed,此时你在壳工程就已经能够使用这个库了,这是因为你拖入第三库ZipArchive时Xcode已经给你配置好Framework Search Paths,你可以在壳工程的builds settings搜索到。如下图:

    但是你此时在壳工程中使用ZipArchive.framework,运行起来是会crash的,奔溃信息和前文一张名为奔溃信息日志的图片是一样的,
dyld: Library not loaded: @rpath/ZipArchive.framework/ZipArchive
  Referenced from: /var/containers/Bundle/Application/74F5D1D3-D6B5-4C68-9629-9CAF6C16F133/BookRoomDemo.app/BookRoomDemo
  Reason: image not found

所以解决方案也就是一样的,在EmbeddedBinaies中添加ZipArchive.framework

  1. 设置BookRoomKit.xcodeproj的framework Search Paths
    路径务必填写正确,我写的相对路径:$(PROJECT_DIR)/../BookRoomDemo/BookRoomDemo
  2. BookRoomKit.xcodeproj的Link Binary With Binaries添加ZipArchive.framework,选在Add Other,添加进来即可。
    接下来你就可以在BookroomKit使用ZipArchive了,具体代码你可以在BookRoomDemo下载。

如果你觉得本文对你有帮助,就请你点亮底部的❤️吧,你的鼓励是我前进的动力!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,565评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,021评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,003评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,015评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,020评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,856评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,178评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,824评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,264评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,788评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,913评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,535评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,130评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,102评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,334评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,298评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,622评论 2 343

推荐阅读更多精彩内容