目录

CocoaPods组件化——OC/Swift动静态库混用

缘起

一个swift库,charts

项目本身是通过 cocoapods 进行组件化管理的。

在没有集成 charts 之前,一切都是那么的美好,天是晴的,雨是停的。

直到有一天,因为业务需要图表功能,经一番调研之后,选择了 charts 集成到工程之中。

然后噩梦就开始了。

直接集成跑项目会直接报上百个错,因为 charts 是用swift写的,错误简要如下:

1
2
3
4
5
6
7
Undefined symbols for architecture x86_64:
  "protocol witness table for Swift.Double : Swift.CVarArg in Swift", referenced from:
      static Charts.XFChartString.format2f(number: Swift.Double) -> Swift.String in libCharts.a(XFChartUtils.o)
  "base conformance descriptor for Swift.Comparable: Swift.Equatable", referenced from:
      protocol conformance descriptor for Charts.(LineAlt in _35CAD2F96B83BFC8E7052E13186FFBAD) : Swift.Comparable in Charts in libCharts.a(DataApproximator+N.o)
  "method descriptor for static Swift.Comparable.< infix(A, A) -> Swift.Bool", referenced from:
      protocol conformance descriptor for Charts.(LineAlt in _35CAD2F96B83BFC8E7052E13186FFBAD) : Swift.Comparable in Charts in libCharts.a(DataApproximator+N.o)

当时没想太多(这就是过于依赖搜索了,连分析都没直接去搜),搜的如何的oc项目通过 pod 集成swift代码。

大概翻了几篇的意思是swift只能动态库打包,需要 pod 管理的库改成动态库,其实这都是老黄历了,都是 pod1.5 之前的方案,不过我当时还是无脑跟着这么做了。

其实在这时候里胜利只差一步,结果绕了一个大弯子。如果不想看我废话这么多的,可以直接转到结局去看。

use_frameworks!

Podfile中加入 use_frameworks!,就可以让 pod 管理的库都改成动态库。

加上之后,重新 pod install 一下。

然后项目一跑,果然不报错了,哎呦,牛逼,这就解决了。

心里美滋滋的,现在回头来看,还是太年轻了,直接把库都弄成动态库,后面还有成吨的依赖问题在等着我。。

就如下面的标题: 动静态库 pod 内部依赖静态lib,静态framewor,动态framework,都是坑!

动态库 Pod 中嵌入静态 lib

先说一下最先遇到的就是动态库 Pod 中嵌入静态lib。

vendored_library属性对应依赖的.a,然后依赖系统库在library,frameworks里加上。

最后就是.h,如果你不想暴露的话public_header_files里加完就不用管了,如果想要暴露给别人调用,只能source_files里再加一遍.h。

不想在source_files里再写一遍的也可以建个.h引用一遍所有.a的头文件,最后source_files写你自己的.h,但这只是保证我到处可以通过引用自己的头文件实现方法调用,并不能单个引用对应.a的头文件。

动态库 Pod 嵌入静态 Framework

对静态的Framework封装的时候可以说是最简单的了,vendored_frameworks加上去基本就万事大吉了,至于依赖啥系统库什么的跟上面一样。

动态库 Pod 嵌入动态 Framework

对于动态的Framework封装,是最恶心的,就算不用 pod 也是很麻烦的。

不用 pod 你要手动把这SDK拖到Embedded Binaries位置头文件才能引用,这个是苹果现在引用动态Framework的方法。

下面讲一下 pod 怎么搞,如果单纯framework做 pod,首先public_header_files要指定xxx.framework/Headers/{.h}不然头文件找不到。

其次source_files里看具体编译情况决定加不加xxx.framework/Headers/{.h},然后就是比较普通的地方vendored_frameworks指定好就完事了。

source_files这个加了的时候还有一个前提就是Framework内引用全是"“不能<>,所以大部分情况source_files不加。

另一种混合使用感觉这才是最常见的。

这时候不要指定Framework的public_header_files,写一个自己的头文件引用类,把想公开可以调用的写在在头文件引用类里如:#import <xxx.framework/xxx.h>,间接把xxx.h暴露出来。

静态库 Pod 资源文件的调用

静态库其实还好,跟 pod 有关的resourced都在相应的 pod库名.bundle 之中。 只需要用下面方法,就可以取出bundle,然海找相应的资源。

1
2
3
4
5
6
+ (NSBundle *)staticLibBundleWithModuleName:(NSString *)moduleName {
    
    NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:moduleName withExtension:@"bundle"];
    NSBundle *bundle = [NSBundle bundleWithURL:bundleURL];
    return bundle;
}

动态库 Pod 资源文件的调用

每错,新坑出来了,动态 pod 中的静态framework,所依赖的资源图片找不到了。

打开ipa一看,路径全变了,pod 建动态库的时候的bundle都在相应的Framework里了。

大概路径就是:xxx.ipa/Frameworks/xxxpod.framework/xxxpod.bundle

真坑。。

所以获取bundle的方法改成下面的了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
+ (NSBundle *)dynamicLibBundleWithModuleName:(NSString *)moduleName {
    
    NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"Frameworks" withExtension:nil];
    bundleURL = [bundleURL URLByAppendingPathComponent:moduleName];
    bundleURL = [bundleURL URLByAppendingPathExtension:@"framework"];
    bundleURL = [bundleURL URLByAppendingPathComponent:moduleName];
    bundleURL = [bundleURL URLByAppendingPathExtension:@"bundle"];
    
    NSBundle *bundle = [NSBundle bundleWithURL:bundleURL];
    return bundle;
}

关于动静之争

关于动态库和静态库的优劣网上一堆一堆的文章在说,这里就不废话了。

不过我个人倾向是用静态库的,事少,而且启动速度一些,说白了就是空间换时间。

安卓的art技术也是在空间换时间。

这年头空间哪有时间贵啊,手机容量再小,连这点都没有的那也不是我们的有效客户了。

结局

故事的答案是,我一开始以为swift不能打成静态库,所以导致的报错,其实并不是的。

当时报这个错是 Undefined symbols 错误,在后来把报错一搜,找到了 stackoverflow 上一个帖子

大概意思就是这个OC的项目不知道要编译Swift的代码才报错,只需要在主工程中,建一个空的swift文件并自动建一个bridge,然后就可以了。

没错,智慧就是这么简单,而我绕了好大的一个弯。

彩蛋1:Xcode9swift静态库的支持

一开始查的结果其实就是过时的,早在Xcode9,swift就支持打成静态库了,所以不用非要弄成动态库。

彩蛋2: pod1.5 use_modular_headers!

随着支持swift静态库,pod1.5也更新的对应的功能,如果swift的 pod 依赖于某个OC的 pod,需要为该OC版 pod 启用modular headers,所以多了 use_modular_headers!来全局开启,不过开启之后,之前一些不严谨的依赖,可能会报错,需要具体情况具体分析了,网上相关的文章也很多,就不在这里一一赘述了。而且我也不建议这种跨语言的交叉依赖,比如我的项目主要是OC,依赖的swift版 pod,就是纯swift写的。

再来点废话

虽然是过去式了,但是之前用 use_frameworks! 的时候,发现会给每一个 pod 创建一个 umbrella.h 文件,而这个文件里会有 pod 里所有的.h,当然这个是可以通过 podspec 中的属性控制的。如果 pod 里有 cpp 之类的文件,各种引用问题就很烦人了,还得一点一点改。

这是去年建新项目的时候一点心路历程,只是大概记个印象了,有的细节也不是很清楚了,等后面再遇到或者想起来了,再补充把。

引用:

https://stackoverflow.com/questions/52536380why-linker-link-static-libraries-with-errors-ios https://cloud.tencent.com/developer/news/252403 https://www.jianshu.com/p/10ed66dae403 https://blog.csdn.net/ios8988/article/details/84111011 https://www.jianshu.com/p/544df88b6a1e https://www.jianshu.com/p/be9c848d050f https://www.jianshu.com/p/4be1ef1dc3ff https://www.jianshu.com/p/dfe9a1e1db7f https://www.jianshu.com/p/913df8cc1f18 https://www.jianshu.com/p/35db14a4931c https://zhuanlan.zhihu.com/p/50571342