[迁移背景故事]:
国庆前把之前一个已经发布的工程改了下配置重新上架,结果编译死活通过不了,报的错主要是Segmentation fault: 11
与Command CompileSwift falied with a nonzero exit code :
,项目初次遇到这个问题时是在第一次打包时,当时查阅资料说是 Xcode 的 Bug 清除缓存重新编译就顺利上线了。这次网上各种方法试了个遍都没效果,好在是个小工程不复杂,项目在做模块化的时候使用 CocoaPods 配合 framework 拆分了基础模块和公共业务。最后迫不得已将分拆的基础模块又导入回主工程下,再次编译,发现是 MJRefresh 给组件附加的 header 和 footer 在调用时没有加上可选标识的问号,修复后编译通过。
推测是因为 MJRefresh 由 Objective-C 编写而成,之前的版本可能是旧版本 Xcode 对 OC 类型进行
可选检查
没有深入到子模块,最新版本的 Xcode 新增了对 OC 对象更深入的检查却没有对子模块响应的提示,以至于我们打开 Swift 报错检查等操作并没有具体信息。好巧不巧,这几天鼓捣了下 SwiftPM 后,这个为上架被迫临时还原的项目正好可以拿来试试 SwiftPM 的成色如何。本文记录了该工程从 CocoaPods 迁移到 SwiftPM 过程中遇到的问题以及解决办法。
[阅读难度:简单]
以笔者的小工程为例,CocoaPods 导入的三方库大约十来个,包含了Swift与OC两种类型的库,这些库对 SwiftPM(Swift Package Mananger)的适配状态也不尽相同。迁移的过程从最简单的情况开始一个一个解决。
Swift 类型的库
Swift 类型的库属于最简单的情况,直接用 SwiftPM 引入即可,诸如 Alamofire
、Kingfisher
、SnapKit
等库。对 Xcode 中如何使用SwiftPM 还不了解的同学可以参考官方文档或者 上一篇文章
Objective-C 类型的库
SwiftPM 本是专门针对 Swift 诞生的,由于历史原因,很多库是由OC编写而成,如何引入OC的包?可以分为两种情况:
1. 支持 SwiftPM 的库
这一类跟Swift的库一样直接导入就可以了,这里可以借鉴 MBProgressHUD 库的官方实现,在其 Package.swift
文件中配置如下:
import PackageDescription
let package = Package(
name: "MBProgressHUD",
products: [
.library(name: "MBProgressHUD", targets: ["MBProgressHUD"])
],
targets: [
.target(
name: "MBProgressHUD",
dependencies: [],
path: ".",
exclude: ["Demo"],
sources: ["MBProgressHUD.h", "MBProgressHUD.m"],
publicHeadersPath: "include"
)
]
)
大体和 Swift 库一致,特别注意 sources
这里,库的 OC 文件都列在了这里,publicHeadersPath
则是需要公开的头文件目录,自定义OC库想了解详细设置可查看官方文档或参考 MBProgressHUD
的实现。
但这种方式有个问题,对于MBProgressHUD这样个位数文件的OC库实行起来倒是没什么影响,如果是 MJRefresh 这样几十个文件还附带资源文件怎么办?可能这也是MJRefresh到目前为止依旧没有适配SwiftPM的原因之一。(在最新的 Swift5.3 中已经支持导入资源文件,SE-0271)
2. 未支持 SwiftPM 的 OC 库
最直接的方法是自己创建一个 Swift Package 的中间库,自行配置Package.swift与各种文件。就像上面说的,这种方法耗费太多的时间,随着库的发展,三方库大改,中间库的配置就要大改,想到这里肠胃一阵蠕动, Pass。
那就曲线救国,在 Swift 为主的工程中,引入的 OC 库都比较少,且都是经得起时间考验的精品库,没有适配 SwiftPM 的就更少了。可以将这类 OC 库打包成 Framework 直接丢进项目中即可。这里笔者借助了 CocoaPods 的一个插件 cocoapods-binary,因为CocoaPods在安装三方库时会将其所以来的库一并安装,省去不少麻烦。具体过程如下:
1. 安装 cocoapods-binary:
$ gem install cocoapods-binary
2. 新建个空工程。
3. 创建Pod文件(以MJRefresh为例):
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '13.0' // 这里改为指定系统版本
inhibit_all_warnings!
use_frameworks! // 动态库
plugin 'cocoapods-binary' // 插件
all_binary! // 插件应用于全部库
abstract_target 'BASE_POD' do
pod 'MJRefresh'
target 'StaticLibDemo' do
project 'StaticLibDemo.xcodeproj'
workspace 'StaticLibDemo.xcworkspace'
end
end
4.在当前工程下调用 pod install
命令
**5.生成的库在生成的 Pods 文件下,路径为: **
/Pod/_Prebuild/GeneratedFrameworks/
6.将其中生成的库复制到我们的项目中(如:MJRefresh.framework,仅仅是framework,别的不要,否则发布报错)
这里生成的库会有 x86_32
架构,在发布时会报 IPA progress fail
,需要将此架构从库中移除。
具体方法:
- 命令面板跳转到framework:
$ cd /XXX/XXXX/包名.framework
- 查看信息:
$ lipo -info 包名
,这里会显示出 i386、x86_64- 移除:
$ lipo -remove i386 包名 -o 包名
$ lipo -remove x86_64 包名 -o 包名
- 再次查看信息确认移除成功后即可将包导入工程。
7.因为我们生成的是动态库,所以别忘了在 General 中将导入的动态库设置为“embed & sign”
至此,我们就可以正常替换之前在 CocoaPods 引入的 MJRefresh 了,这只是权宜之计,有更好的办法的小伙伴欢迎补充 [猛男微笑.gif]。
遇到的问题
用 SwiftPM 导入库后,编译报错 not found 'xxxxx'
建议备份一下工程,这类错误的原因不一定相同。
笔者解决方法:通过删除 Build setting 下 Other Linking 的所有配置可编译正常
RxSwift 的导入问题
因为工程以来的基础模块应用到了 RxSwift 所以基础模块的SwiftPackage依赖于RxSwift,然而这个库目前(20201015)对SwiftPM的支持是有问题的(相关链接)。而这导致使用 RxSwift 给出的配置来配置项目时,Xcode无法识别,也无法在
按照官方方法配置子模块的 Package.swift:
import PackageDescription
let package = Package(
name: "RJSuger",
platforms: [.iOS(.v13)],
products: [
.library(
name: "RJSuger",
targets: ["RJSuger"]),
.library(
name: "RJBaseWidgets",
targets: ["RJBaseWidgets"]),
],
dependencies: [
.package(url: "https://github.com/SnapKit/SnapKit.git", .upToNextMajor(from: "5.0.1")),
.package(url: "https://github.com/ReactiveX/RxSwift.git", .exact("6.0.0-rc.1"))
],
targets: [
.target(
name: "RJSuger",
dependencies:[],
path: "Sources/RJSuger"),
.target(
name: "RJBaseWidgets",
dependencies: ["RJSuger","SnapKit","RxSwift", "RxCocoa"],
path: "Sources/RJBaseWidgets"),
.testTarget(
name: "RJSugerTests",
dependencies: ["RJSuger"]),
],
swiftLanguageVersions: [.v5]
)
这样的配置对于笔者的模块,Xcode 识别不出来,报出来的错误指向了 最后添加的依赖项 RxCocoa,参考 RxSwift的文件结构是RxSwift下有 RxSwift、RxCocoa、RxRelay等模块,尝试性的将对应的 Targets 依赖变更为:
//····
dependencies: ["RJSuger","SnapKit",.product(name: "RxSwift", package: "RxSwift"),.product(name: "RxCocoa", package: "RxSwift")],
//···
编译通过。
总结
从 2018 年推出 SwiftPM 到现在,SwiftPM 已经可以应付大部分库管理的场景,对自定义模块的支持也不错,不出问题的时候使用起来真是清爽自然。而当遇到问题时,网络上资料可能很少,也可能直接就是三方库或 SwiftPM 本身的问题不得不另寻他法。最最最重要的问题是加载库的速度和机制
,稍微大点的库本身速度就慢,稍微变动下工程结构,SwiftPM 就莫名所有库重新加载一次,导致大量时间浪费在这上面,而SwiftPM 的 checkout 下来的库对存在 DriveData/工程文件/SourcePackages/checkout下
,对于老夫这种喜欢清理 DriveData 的人不得不备份一份在其抽风时替换。
所以个人建议:SwiftPM 建议了解下,实际应用到项目,胆子小的:
再等一年又何妨
Thanks for reading :)