开发环境:

  • Xocde 9.4

  • CocoaPods 1.5.3

在 CocoaPods 1.5 之后终于开始支持打包静态库了,在上一篇启动优化的文章中介绍了动态库和静态库的区别。动态库的优势在于代码和资源共享,在一定程度上会减小 archive 的包,但是在 App 启动的时候需要加载连接,所以会影响启动时间。而静态库也就是我们编译好的二进制文件,不能共享代码和资源,所以在支持打包静态库之后,包大小变大,但是启动时间改善了很多。

我们目前项目中仅仅使用 CocoaPods 管理第三方依赖。支持打包静态库就是在 Podfile 文件中加入 use_modular_headers,CocoaPods1.5 新增的变量。在 1.5 之前继承第三方的 swift 项目,需要在 Podfile 中使用 use_framework 。这个变量是告诉 CocoaPods 打包成 Framework 而不是静态库,因为那时候的 swift 还不支持静态库打包。在 Xcode9 提到会加入支持。

New in Xcode 9 – Swift static libary support.

  • Added support for static library targets that contain Swift code

以下是 Xcode 9 beta 4 的 release note 的描述

The new build system supports static library targets which contain Swift code. Debugging applications which use Swift static libraries may require having a complete set of build artifacts (in their original location) available. (33297067)

1
2
3
4
<unknown>:0: error: module 'xxxx' in AST file '.../DerivedData/ModuleCache.noindex/21TNG3F8V3NTA/xxxx-2FQD6HF52EM16.pcm' (imported by AST file '.../DerivedData/yyyy-csmyvsfszhqthadhoioogvzklyci/Build/Intermediates.noindex/PrecompiledHeaders/yyyy-Bridging-Header-swift_6ZKU62Z1BIDH-clang_21TNG3F8V3NTA.pch') is not defined in any loaded module map file; maybe you need to load '.../Pods/Headers/Public/xxxx/xxxx.modulemap'?
<unknown>:0: note: consider adding '.../Headers/Public/xxxx' to the header search path
<unknown>:0: note: imported by '.../DerivedData/yyyy-csmyvsfszhqthadhoioogvzklyci/Build/Intermediates.noindex/PrecompiledHeaders/yyyy-Bridging-Header-swift_6ZKU62Z1BIDH-clang_21TNG3F8V3NTA.pch'
<unknown>:0: error: clang importer creation failed

从上面的错误来 module xxxx 在 module map 文件中没有定义,因为我们在 pch 文件中 import 了 framework 的头文件。但是在使用 use_modular_headers 的时候,CocoaPods 将第三方的依赖库打包成 Modular,在使用这些第三方库时,直接 import 就可以直接使用了,而不需要在 bridging 文件中在 #import<> 。下一步就是从 bridging 文件中删除相关的 #import<> ,然后在 swift 源文件中加入 import xxx。然后在 build 下。

经过 “漫长“ 的 build 时间,又冒出一个 error

1
2
ld: framework not found Alamofire
clang: error: linker command failed with exit code 1 (use -v to see invocation)

在 link 阶段没有找到 Alamfire 库,首先我去检查了 Xcode 的 build 日志,发现 CocoPods 的第三方库都编译成功了,相应的目录下也有 .a 文件。说明是 search paths 的问题或者是 other linker flag 少了参数。查看在 Pods-panpan.debug.xcconfig 文件里的参数之后,在项目 target 的 build setting 里的对应参数。然后我们把 library search pathsframework search paths 把相关参数都删掉除了 $(inherited) 。然后在 build ,如果还没有功能的话,可以再在 other linker flag 尝试相同的操作。

这里的原因是在 xcconfig 在 build 的时候是有继承链的,关系是:项目的 xcconfig -> pods.xcconfig -> target 的 xcconfig 从右向左依次覆盖。在 target 的 build setting 中覆盖了 pods.xcconfig 导致关于库在搜索路径的时候找不到。

这样就顺利 build 成功了,收益就是在 App 启动 pre-main 时间减少的比较明显,从之前的 600ms 减少到 300ms,这里的 300ms 是比较不稳定,平均减少大概在 100ms~300ms。关于包大小,在 archive 导出的 ipa 包的大小增加的 8M,这个比较容易理解,使用静态库打包导致部分代码和资源不能像动态库一样可以共享,所以一些大项目为了不达到 App Store 150M 的下载限制,被迫使用动态库。

总结

在这个解决 error 过程和查找资料的过程中还是需要花一些时间,包括在 CocoPods1.5.3 扔存在一些问题和第三方库也存在某些问题,中间就遇到 Lottie 的 specs 文件中的 module_name 与包名不一致的问题。在这个过程中同样也对 xcode 编译构建有了更好的理解。对于项目的收益来讲,同样也是值得的,打破了之前在 App 启动时间遇到的部分瓶颈。如果你们还没有支持 CocoaPods 打包静态库的话,快去尝试吧!

参考

what‘s new in xcode 9
xcode release note

应用 Swift 静态库的各种坑