将工程从 CocoaPods 迁移到到 SwiftPM

[迁移背景故事]:
国庆前把之前一个已经发布的工程改了下配置重新上架,结果编译死活通过不了,报的错主要是Segmentation fault: 11Command 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 引入即可,诸如 AlamofireKingfisherSnapKit 等库。对 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,需要将此架构从库中移除。

具体方法:

  1. 命令面板跳转到framework: $ cd /XXX/XXXX/包名.framework
  2. 查看信息: $ lipo -info 包名,这里会显示出 i386、x86_64
  3. 移除:
    $ lipo -remove i386 包名 -o 包名
    $ lipo -remove x86_64 包名 -o 包名
  4. 再次查看信息确认移除成功后即可将包导入工程。

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 :)

你可能感兴趣的:(将工程从 CocoaPods 迁移到到 SwiftPM)