原文地址 juejin.im
从更新到现在,SwiftPM 令人诟病的一个问题就是无法在包里添加资源文件。这对于已经习惯于使用 CocoaPods 的开发者造成了很大的麻烦,当然目前 SwiftPM 差于 Cocoapods 不止这一点。SwiftPM 也意识到了这一点,从去年就可以看到 github 的 SwiftPM 对应仓库的有 resource
等 API 相关提交。
此次的 SwiftPM 更新中除了上面说的可以添加资源文件,还添加了本地化等功能。下面介绍下 WWDC-2020 上该 Session 的内容。
配置要求
SwiftPM 的资源文件管理功能在 swift-tool-version 5.3,即 Swift 5.3,对应 Xcode 12。所以 package.swift
配置中需要声明 swift 5.3 以上 (这行并不是注释,而是文件解析中必须的配置):
添加和配置资源文件
对于一些使用目的明确的文件类型,比如下面图中的这些。开发者不需要在 package.swift
文件中配置任何东西,因为 Xcode 知道这些类型的文件是代表什么,比如 .xcassets
文件代表图片、颜色资源, xib
代表用户界面文件等
而对于一些使用目的不太明确的文件类型(如下图中的一些文件类型),则需要在 package.swift
文件中配置。例如纯文本文件,这种文件中的数据可能是需要在运行时被加载而计算或者展示,也可能只是一个开发者文档。
对于上面这种意义不明的文件,就需要在 package.swift
清单中根据规则配置,下面以这个 GameLogin 作为例子:
- 对于
Media.xcasset
和 main.storyboard
文件,Xcode 能明确知道它代表什么,所以不需要在这个配置文件中标记
internal Note.txt
文件和 Artwork Creation
文件夹是模块内部文件,所以写在 target
的 exclude
属性中,这样 Xcode 就不会把它编译到包里
- 其他不能自动识别的类型并且需要被加载到 package 里的文件则配置在
resource
属性中。
上面就是配置资源文件的一些规则,其中我们可以看到对于 resource
属性,有两个静态方法: process()
和 copy()
。根据 session 中的介绍, process()
是推荐的方式,它所配置的文件会根据具体使用的平台和内置规则进行适当的优化。比如在运行时将 storyboard
或者 asset catalog
转换成适当的形式,也包括压缩图片等。如果文件类型无法识别,或者不能根据平台做任何优化,就只会被简单的拷贝,也就是 copy()
。
构建过程
当一个 App 使用 package 时,这个 package 包括源文件和资源文件。在编译时首先会将 Package 中每个 target 的源文件编译成 module 链接到 App 中,然后这些 target 中的资源文件则会被加工成 bundle 放到这些 module 中。
在 Apple 平台中,App 和 App extension 都是 bundle 集合,这些 package 的 bundle 就是 App 的一部分,所以不需要做其他处理,就能在运行时获取这些 bundle。 当被编译到一个 unbundle 产物时,比如脚本工具,则需要在脚本启动的同时加载资源 bundle(这一步的具体步骤还不太理解)
访问资源文件
在编译有资源文件的 Package 中,会自动创建并添加到 module 中一个文件: resource_bundle_accessor.swift
,里面的内容大概等价于下面这样:
import Foundation
extension Bundle {
static let module = Bundle(path: "(Bundle.main.bundlePath)/path/to/this/targets/resource/bundle")
}
复制代码
对于 Swift 和 OC 分别可以使用下面这种方式,当然也可以使用 UIImage 自己的带有 Bundle
参数的 Api
由于 module
是内部属性,所以这种方式只能访问自己模块内部的资源文件,无法跨模块访问。如果想在一个公共模块提供外部模块使用的资源,则需要自己创建一个资源访问器。关于这一点,使用过 Cocoapods 的 resource_bundle
功能的开发者可能比较了解,可以采用 bundle 路径方式访问。如果不单独建立一个公共资源模块,则不需要考虑这么多。
本地化
首先需要在配置文件中配置默认的语言:
let package = Package(
name: "MyLibrary",
defaultLocalization: "en",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
)
复制代码
然后根据你需要的语言创建对应的文件夹,文件名为对应的语言,后缀命名成 .lproj
,并在文件夹中创建 .strings
或者 .stringsdict
文件,如下图所示:
使用时:
Button(action: roll, label: {
Text("Roll", bundle: Bundle.module)
.font(.title)
})
复制代码
在测试时可以通过声明环境变量设置语言环境:
从文件夹声明中可以看出,语言国际化需要新建一个后缀名为 .lproj
的文件夹,这也符合上面说的 “具有使用目的的文件”,所以这里不需要在 package.swift
中额外配置属性。
总结
这个简短的内容主要介绍了如何在 Package 中添加本地资源文件和本地化的过程,大概是几个要点:
- 对于使用目的明确的文件,比如以
.xcassets
、 .xib
、 .storyboard
等为后缀的文件,不需要在 package.swift
中添加任何配置。
- 对于用途不明确的文件,比如纯文本文件、脚本文件,则视情况在
package.swift
中使用不同属性配置(以下均是文件、文件夹均可配置):
- 对于不需要被外部引用的,例如内部的开发者文档
README
,需要配置在 target.excludes
属性中。
- 对于运行时有用到,可以被系统根据平台优化的文件,比如各种图片,需要配置在
target.resource.process
属性里
- 对于运行时有用到,不存在优化的文件,比如各种图片,需要配置在
target.resource.copy
属性里
- 本地化过程,首先需要在配置文件中声明默认语言,然后根据语言创建
.lproj
文件夹,再在文件夹里创建 .strings
或者 .stringsdict
文件,写上本地化的字符串。
除了上面这些内容,回到 Swift Package Manager 本身,尽管在日渐完善,但是个人觉得距离广泛使用真的很遥远。当前的环境下,swift 虽然在国内几年内会基本替代 OC 成为 iOS 原生主要开发语言,但整个环境并没有合适的理由阻止跨端开发逐渐成为主流。从 H5 到 RN、weex,再到现在的 flutter,再映射到配置不断优化的硬件上 性能差距慢慢被缩小,而对业务所希望的热更新也不是技术问题。对于剩余的 Swift 开发者中,SwiftPM 该如何说服他们放弃成熟的 CocoaPods 来使用它呢?仅仅靠一个 “官方” 的标签远远不够。
引用