二维火云收银iOS客户端使用了Objective-C和Swift混编,在Xcode9(2017年9月发布)以前苹果不支持使用Swift Static Libraries。 同时,咱们使用了CocoaPods进行项目管理,对于Swift+CocoaPods的项目直到2018年4月发布的Cocoapods1.5.0才官宣支持把Swift Pods构建成Static Libraries。因此在CocoaPods1.5.0以前咱们一直使用的是Dynamic frameworks。git
动态库与静态库的区别、优劣不在本文讨论范围以内,相关的文章网上能够搜到不少。本文主要记录了咱们为何要转向转向Swift Static Libraries ,以及迁移过程遇到的一些问题和思考。github
pod install
的时候会产生has transitive dependencies that include static binaries
d的error。因此一直以来咱们都是苦逼的做一层包装,把第三方库作成私有的Dynamic framework而后接入到工程中(若是谁有更好的处理方法,但愿获得指导)。然而,就是这个二次包装的过程咱们也踩了不少坑,其中印象深入的是Archive的时候遇到了bitcode问题。调试运行正常的代码在最终打包预备发布的时候遇到下面错误:bitcode bundle could not be generated because xxx was built without full bitcode.
All object files and libraries for bitcode must be generated from Xcode Archive or
Install build for architecture armv7
复制代码
记得当时翻了一下午Google,最终在这里找到了答案,是对BITCODE_GENERATION_MODE
没有正确设置,在这里再次感谢一下文章做者。bash
当看到CocoaPods官宣“CocoaPods 1.5.0 — Swift Static Libraries”时,咱们欣喜的觉得迁移Static libraries的时机终于到了。
官宣官宣:post
With CocoaPods 1.5.0, developers are no longer restricted into specifying use_frameworks! in their Podfile in order to install pods that use Swift. Interop with Objective-C should just work. However, if your Swift pod depends on an Objective-C, pod you will need to enable "modular headers" (see below) for that Objective-C pod.性能
简单来讲就是:
从CocoaPods 1.5.0起,开发者再也不限定于在他们的Podfile中使用use_frameworks!来安装使用Swift的Pods。 与Objective-C的互操做应该像以前同样可以正常工做。 可是,若是你的Swift pod依赖于某个Objective-C的 pod,你须要为该Objective-C pod启用“modular headers”。ui
感受咱们的春天终于来了,因而趁着项目缓冲的功夫,召集你们搞起!搞起!this
迁移步骤基本参照了CocoaPods的官方指导,具体有如下几点:url
use_frameworks!
(一直以来的噩梦!), 改用添加use_modular_headers!
,这将开启严格的header search path
。官方原文是:When CocoaPods first came out many years ago, it focused on enabling as many existing libraries as possible to be packaged as pods. That meant making a few tradeoffs, and one of those has to do with the way CocoaPods sets up header search paths. CocoaPods allowed any pod to import any other pod with un-namespaced quote imports.
For example, pod B could have code that had a #import "A.h" statement, and CocoaPods will create build settings that will allow such an import to succeed. Imports such as these, however, will not work if you try to add module maps to these pods. We tried to automatically generate module maps for static libraries many years ago, and it broke some pods, so we had to revert. In this release, you will be able to opt into stricter header search paths (and module map generation for Objective-C pods).spa
翻译过来是:翻译
当CocoaPods多年前首次问世时,它专一于使尽量多的现有库被打包为pods。这意味着作出一些权衡,其中一个就是CocoaPods建立头文件搜索路径的方式。CocoaPods容许任何pod以无命名空间的方式引用其余pod。 例如,pod B可能有某个文件包含有#import“A.h”这样的代码,CocoaPods将建立构建设置以容许此类导入成功。 然而,若是您尝试将module maps添加到pods中,这种导入方式就会失败。 多年前咱们曾尝试为静态库自动生成module maps,这破坏了一些pod,致使咱们不得不放弃。 在CocoaPods1.5.0版本中,您将可以选择更严格的头文件搜索路径(以及为Objective-C pods生成module maps)。
实际过程当中,发如今Podfile中使用了use_modular_headers!
的结果是不少之前有效的Pod间的头文件引用会在编译期间报错。为解决这个问题,咱们依次进行了如下几步:
:modular_headers => false
,这样能够避免一些编译错误,减小迁移的工做量。@import B
,同时又引了自身pod的一个文件AA.h,在AA.h中又有 @import B
或者 #import<B/B.h>
这样的代码。这将致使A.m中产生相似ambiguous reference
的编译错误。解决方法也比较简单,经过删除一些重复引用来解除引用模糊就行了,可是因为涉及到的pods和文件比较多,花费了咱们不少时间。The Swift pod `xxx` depends upon `aaa`, `bbb`, `ccc`, which do not define modules. To opt into those
targets generating module maps (which is necessary to import them from Swift when building as
static libraries), you may set `use_modular_headers!` globally in your Podfile,
or specify `:modular_headers => true` for particular dependencies.
复制代码
完蛋,若是要对第三库也进行相关的修改,那将是一个浩大的工程。stack overflow上也有人遇到了相同的问题。无奈,咱们只好暂时停止整个迁移过程。
以后,我一直保持着对Cocoapods版本更新的关注,终于盼到了1.6.0Beta。这个版本Cocoapods作了不少性能和稳定性方面的提高,你们能够去官网一览究竟,固然最好本身试一下,pod update快了不少。最引发咱们注意的是在它的release notes里面发现了下面这条关于第三方库的描述:
When integrating a vendored framework while building pods as static libraries, public headers will be found via FRAMEWORK_SEARCH_PATHS instead of via the sandbox headers store.
简单说就是在把pods构建为static librarries时若是集成了第三方的framework,公开的头文件将经过 FRAMEWORK_SEARCH_PATHS来进行搜寻,而不是以前的沙盒内的头文件仓库。
而后咱们重演了阶段一的那些流程,使用最新的1.6.0Beta尝试将咱们的pods构建成static libraries.果真,此次第三方库没有报错。pod install成功了,编译经过了,pods的构建产物由以前的行李箱(xxx.framwork)变成了咱们想要的小房子(xxx.a)O(∩_∩)O哈哈~。可是,我知道咱们离成功还差一步--资源引用问题。
资源引用
iOS中动态库和静态库对资源的管理方式有着显著不一样。这就致使咱们须要在代码中对图片、localizedString等进行引用的时候也须要作一番更改。如下两点是基于咱们在podspec中使用了resource_bundles来进行资源管理。
对于动态库, 图片、.strings文件等资源会放在独立的bundle中,存储在本身所属的pod的最终产物framework下,所以经过main bundle是没法获取的。代码中引用资源时须要先传入本pod的一个class,经过+ (NSBundle *)bundleForClass:(Class)aClass
拿到一个bundle对象,取得此的bundleName后再经过- (NSURL *)URLForResource:(NSString *)name withExtension:(NSString *)ext;
获取bundle的URL,最终经过+ (instancetype)bundleWithURL:(NSURL *)url;
获取目的bundle。实际项目中咱们对上面的系列操做在基础组件中封装了若干个宏,方便业务方调用。
对于静态库则相对简单。资源最终都会以独立的bundle集成到main bundle,代码中须要在mainbundle中根据bundleName找到pod对应的bundle,而后取相应的资源就能够了。基本会有如下的代码:
NSURL *bundleUrl = [[NSBundle mainBundle] URLForResource:bundleName withExtension:@"bundle"];
NSBundle *bundle = [NSBundle bundleWithURL:bundleUrl];
UIImage *image = [UIImage imageNamed:imageName inBundle:bundle compatibleWithTraitCollection:nil];
复制代码
终于写完了,总的看来其实也不复杂,主要的地方其实在于对CocoaPods新特性的使用,还有就是动态库与静态库资源引用方式不一样的理解与适配。水平有限,若是有错误或者描述不清楚的地方,欢迎与我交流。