SwiftGen
SwiftGen 是一个工具,用于自动为项目资源(如图像、本地化字符串等)生成 Swift 代码,使其在使用时具有类型安全性。
然后为以下内容生成常量: |
使用这个工具有多个好处:
- 避免使用字符串时出现任何拼写错误的风险
- 免费的自动补全
- 避免使用不存在的资产名称的风险
- 所有这些都将由编译器确保,从而避免运行时崩溃的风险。
此外,由于使用 Stencil 模板,它是完全可定制的,因此即使它附带预定义的模板,你也可以创建自己的模板来生成符合你的需求和指南的任何代码!
安装
根据你的偏好和需求,有多种方式可以在你的机器或项目中安装 SwiftGen:
下载 ZIP 获取最新版本
- 前往最新版本的 GitHub 页面
- 下载与该版本关联的
swiftgen-x.y.z.zip
文件 - 将 zip 归档的内容解压到你的项目目录中
我们建议你将 ZIP 解压到项目目录中并将其内容提交到 git。这样,所有同事都将为这个项目使用相同版本的 SwiftGen。
如果你将 ZIP 文件解压到项目目录根目录下的一个文件夹中(例如名为 swiftgen
),那么你可以在脚本构建阶段使用以下方式调用 SwiftGen:
"${PROJECT_DIR}/swiftgen/bin/swiftgen" …
通过 CocoaPods
如果你使用 CocoaPods,只需在 Podfile
中添加 pod 'SwiftGen', '~> 6.0'
。
然后执行 pod install --repo-update
(或 pod update SwiftGen
如果你想更新现有的 SwiftGen 安装),以下载并安装 SwiftGen
二进制文件和依赖项到项目旁边的 Pods/SwiftGen/bin/swiftgen
中。
由于你可以在 Podfile
中为 SwiftGen
指定确切的版本,这允许你确保所有同事都将为这个项目使用相同版本的 SwiftGen。
然后你可以在脚本构建阶段使用以下方式调用 SwiftGen:
if [[ -f "${PODS_ROOT}/SwiftGen/bin/swiftgen" ]]; then
"${PODS_ROOT}/SwiftGen/bin/swiftgen" …
else
echo "warning: SwiftGen is not installed. Run 'pod install --repo-update' to install it."
fi
类似地,在文档的其余部分中提到使用
swiftgen
的命令时,请确保使用Pods/SwiftGen/bin/swiftgen
而不是仅仅swiftgen
。
注意: SwiftGen 并不真正是一个 pod,因为它不是你的代码在运行时依赖的库;所以通过 CocoaPods 安装只是一个技巧,它将 SwiftGen 二进制文件安装在 Pods/ 文件夹中,但你不会在 Xcode 的 Pods.xcodeproj 的 Pods/SwiftGen 组中看到任何 swift 文件。这是正常的;SwiftGen 二进制文件仍然存在于 Finder 中的该文件夹中。
通过 Homebrew (系统范围安装)
要通过 Homebrew 安装 SwiftGen,只需使用:
$ brew update
$ brew install swiftgen
这将系统范围安装 SwiftGen。同一版本的 SwiftGen 将用于该机器上的所有项目,你应该确保你的所有同事在他们的机器上也安装了相同版本的 SwiftGen。
然后你可以直接在脚本构建阶段调用 swiftgen
(因为它已经在你的 $PATH
中):
swiftgen …
通过 Mint (系统范围安装)
❗️仅适用于 SwiftGen 6.0 或更高版本。
要通过 Mint 安装 SwiftGen,只需使用:
$ mint install SwiftGen/SwiftGen
从源代码编译 (仅在你需要 `stable` 分支的功能或想测试 PR 时推荐)
这个解决方案适用于当你想构建和安装 stable
的最新版本,并访问可能尚未发布的功能。
- 如果你已安装
homebrew
,可以使用以下命令构建和安装最新提交:
brew install swiftgen --HEAD
- 或者,你可以克隆仓库并使用
rake cli:install
从任何分支构建工具并安装它,这对于在 fork 或 Pull Request 分支中测试 SwiftGen 可能很有用。
构建过程中使用了一些 Ruby 工具,如果你运行的是最新的 macOS,系统 Ruby 可以很好地工作。但是,如果你使用 rbenv
,可以运行 rbenv install
以确保安装了匹配版本的 Ruby。
然后安装 Ruby Gems:
# 如果未安装 bundle,则安装它
gem install bundle
# 从 Gemfile 安装 Ruby gems
bundle install
现在你可以安装到默认位置(无参数)或自定义位置:
# 二进制文件安装在 `./.build/swiftgen/bin`
$ rake cli:install
# - 或 -
# 二进制文件将安装在 `~/swiftgen/bin`
$ rake cli:install[~/swiftgen/bin]
然后你可以使用安装它的二进制文件的路径调用 SwiftGen:
~/swiftgen/bin/swiftgen …
或将 bin
文件夹的路径添加到你的 $PATH
中,并直接调用 swiftgen
。
macOS 10.14.4 之前的已知安装问题
从 SwiftGen 6.2.1 开始,如果在运行 SwiftGen 时出现类似 dyld: Symbol not found: _$s11SubSequenceSlTl
的错误,你需要安装 Swift 5 Runtime Support for Command Line Tools。
或者,你可以:
- 更新到 macOS 10.14.4 或更高版本
- 在
/Applications/Xcode.app
安装 Xcode 10.2 或更高版本 - 使用 Xcode 10.2 或更高版本从源代码重新构建 SwiftGen
配置文件
❗️ 如果你从旧版本的 SwiftGen 迁移,不要忘记阅读迁移指南。
SwiftGen 作为一个单一的命令行工具提供,它使用配置文件来定义要运行的各种解析器(取决于你需要解析的输入文件类型)及其参数。
要创建一个样例配置文件作为适应你需求的起点,运行 swiftgen config init
。
配置文件中描述的每个解析器(strings
, fonts
, ib
, …)通常对应于要解析的输入资源类型(字符串文件、IB 文件、字体文件、JSON 文件…),允许你为这些输入文件的每种类型生成常量。
要使用 SwiftGen,只需创建一个 swiftgen.yml
YAML 文件(手动或使用 swiftgen config init
),然后编辑它以适应你的项目。配置文件应列出要调用的所有解析器,以及每个解析器要使用的输入/输出/模板/参数列表。
例如:
strings:
inputs: Resources/Base.lproj
outputs:
- templateName: structured-swift5
output: Generated/Strings.swift
xcassets:
inputs:
- Resources/Images.xcassets
- Resources/MoreImages.xcassets
- Resources/Colors.xcassets
outputs:
- templateName: swift5
output: Generated/Assets.swift
然后你只需调用 swiftgen config run
,或者简单地 swiftgen
,它就会执行配置文件中描述的内容。
专门的文档详细解释了语法和可能性 - 比如如何向模板传递自定义参数,使用 swiftgen config lint
验证你的配置文件,如何使用备用配置文件,以及其他提示。
还有一些额外的子命令,你可以从命令行调用来管理和配置 SwiftGen:
swiftgen config
子命令帮助你处理配置文件,特别是swiftgen config init
为你的配置创建一个起点,swiftgen config lint
验证你的配置文件是否有效且没有错误swiftgen template
子命令帮助你打印、复制、查找和管理 SwiftGen 捆绑的模板
最后,你可以在 swiftgen
或其子命令上使用 --help
查看详细用法。
不使用配置文件直接调用解析器
虽然我们强烈建议使用配置文件以提高性能(尤其是如果你有多个输出,但也因为它更灵活),也可以使用 swiftgen run
直接单独调用可用的解析器:
swiftgen run colors [选项] 目录或文件1 …
swiftgen run coredata [选项] 目录或文件1 …
swiftgen run files [选项] 目录或文件1 …
swiftgen run fonts [选项] 目录或文件1 …
swiftgen run ib [选项] 目录或文件1 …
swiftgen run json [选项] 目录或文件1 …
swiftgen run plist [选项] 目录或文件1 …
swiftgen run strings [选项] 目录或文件1 …
swiftgen run xcassets [选项] 目录或文件1 …
swiftgen run yaml [选项] 目录或文件1 …
在少数情况下使用这种方式可能会有用 - 相对于使用配置文件而言 - 比如当你正在开发自定义模板时,想要在每次迭代/版本的自定义模板中快速测试特定的解析器,直到你对它满意为止。
每个解析器命令通常接受相同的选项和语法,它们反映了配置文件中的选项和参数:
--output 文件
或-o 文件
: 设置写入生成代码的文件。如果省略,生成的代码将打印到stdout
。--templateName 名称
或-n 名称
: 定义要使用的Stencil模板(按名称,更多信息请参见此处)以生成输出。--templatePath 路径
或-p 路径
: 使用完整路径定义要使用的Stencil模板。- 注意:调用SwiftGen时应只指定一个模板。你必须使用
-t
或-p
之一,但不应同时使用两者(这样做没有意义,如果你尝试这样做会收到错误) --filter 正则表达式
或-f 正则表达式
: 应用于每个输入路径的过滤器。过滤器应用于实际(相对)路径,而不仅仅是文件名。每个命令都有一个默认过滤器,你可以用这个选项覆盖它。- 注意:使用
.+
匹配多个字符(至少一个),如果要匹配文字点(如扩展名),别忘了转义点(\.
)。在末尾添加$
以确保路径以你想要的扩展名结尾。正则表达式默认区分大小写,并且不会锚定到路径的开头/结尾。例如,使用.+\.xib$
匹配扩展名为.xib
的文件。使用像 RegExr 这样的工具来确保你使用的是有效的正则表达式。 - 每个命令支持多个输入文件(或适用的目录)。
- 你始终可以使用
--help
标志查看命令接受的选项,例如swiftgen run xcassets --help
。
选择模板
SwiftGen 基于模板(它使用 Stencil 作为模板引擎)。这意味着你可以选择适合你正在使用的 Swift 版本的模板 - 也是最适合你偏好的模板 - 以使生成的代码适应你自己的约定和 Swift 版本。
内置模板 vs. 自定义模板
SwiftGen 随附了一些适用于每个解析器的模板(colors
, coredata
, files
, fonts
, ib
, json
, plist
, strings
, xcassets
, yaml
),这些模板可以满足大多数需求;只需使用 templateName
输出选项指定要使用的模板名称即可。但如果内置模板不适合你的编码约定或需求,你也可以创建自己的模板:只需将它们存储在任何地方(比如你的项目仓库中),并使用 templatePath
输出选项而不是 templateName
来指定它们的路径。
💡 你可以使用 swiftgen template list
命令列出每个解析器可用的所有内置模板,并使用 swiftgen template cat
显示模板内容并复制它以创建自己的变体。
有关如何创建自己的模板的更多信息,请参阅专门的文档。
SwiftGen 随附的模板:
如上所述,你可以使用 swiftgen template list
列出 SwiftGen 随附的所有模板。对于大多数 SwiftGen 解析器,我们提供:
- 一个
swift4
模板,兼容 Swift 4 - 一个
swift5
模板,兼容 Swift 5 - 其他变体,如用于 Strings 的
flat-swift4/5
和structured-swift4/5
模板等。
你可以在这里的仓库中找到每个内置模板的文档这里,文档按每个 SwiftGen 解析器组织为一个文件夹,然后每个模板一个 MarkDown 文件。你还可以使用 swiftgen template doc
直接从终端在浏览器中打开该文档页面。
每个 MarkDown 文件记录了它针对的 Swift 版本,该模板的用例(在哪些情况下你可能会选择该模板而不是其他模板),调用时可用的自定义参数(使用配置文件中的 params:
键),以及一些代码示例。
不要犹豫,提出 PR 分享你对内置模板的改进建议 😉
其他文档
Playground
本仓库中提供的 SwiftGen.playground
允许你使用该工具通常生成的代码进行试验,并查看一些如何利用它的示例。
这允许你快速了解 SwiftGen 生成的典型代码的样子,以及如何在代码中使用生成的常量。
Markdown 专用文档
本仓库以及相关的 StencilSwiftKit 仓库中有大量以 Markdown 格式提供的文档。
请务必查看每个仓库的 "Documentation" 文件夹。
特别是,除了前面提到的迁移指南和配置文件文档外,SwiftGen 仓库的 Documentation/
文件夹还包括:
- 一个
templates
子目录,详细说明了 SwiftGen 随附的每个模板的文档(何时使用每个模板,输出会是什么样子,以及自定义参数以调整它们,...) - 一个
SwiftGenKit Contexts
子目录,详细说明了"Stencil 上下文"的结构,即解析输入文件后得到的字典/YAML 表示。这份文档对想要编写自己的模板的人很有用,这样他们在编写模板时就知道可用的结构和各种键,以相应地构造所需的生成输出。 - 各种文章提供最佳实践和技巧,介绍如何在项目中更好地利用 SwiftGen:
- 将 SwiftGen 集成到你的 Xcode 项目中 — 这样每次构建时它都会重新生成常量
- 配置 SwiftLint 以帮助你的开发人员使用 SwiftGen 生成的常量
- 创建自定义模板,以及监视文件夹以在每次保存正在处理的模板时自动重新生成输出
- ...等等
教程
你还可以在互联网上找到其他帮助和教程材料,比如我在 2017 年 9 月 FrenchKit 上给出的这个关于代码生成的课堂 — 以及其 wiki 详细介绍了关于安装和使用 SwiftGen(以及 Sourcery)的分步教程
可用的解析器
Asset Catalog
xcassets:
inputs: /dir/to/search/for/imageset/assets
outputs:
templateName: swift5
output: Assets.swift
这将生成一个 enum Asset
,每个资产(图像集、颜色集、数据集等)都有一个 static let
,这样你就可以将它们用作常量。
使用示例
// 你可以通过引用枚举实例并在其上调用 `.image` 来创建新图像:
let bananaImage = Asset.Exotic.banana.image
let privateImage = Asset.private.image
// 你可以通过引用枚举实例并在其上调用 `.color` 来创建颜色:
let primaryColor = Asset.Styles.Vengo.primary.color
let tintColor = Asset.Styles.Vengo.tint.color
// 你可以通过引用枚举实例并在其上调用 `.data` 来创建数据项:
let data = Asset.data.data
let readme = Asset.readme.data
// 你可以使用以下方式加载 AR 资源组的项目:
let bottles = Asset.Targets.bottles.referenceObjects
let paintings = Asset.Targets.paintings.referenceImages
// 你可以通过引用枚举实例并在其上调用 `.image` 来创建新的符号图像(带配置或不带配置)
let plus = Asset.Symbols.plus.image
let style = UIImage.SymbolConfiguration(textStyle: .headline)
let styled = Asset.Symbols.exclamationMark.image(with: style)
Colors
❗️ 我们建议在 Assets Catalogs 中定义颜色,并使用
xcassets
解析器(见上文)生成颜色常量,而不是使用下面描述的colors
解析器。
如果你支持无法在 Asset Catalogs 中定义颜色的较旧版本的 iOS,或者想要使用 Android 的colors.xml
文件作为输入,那么下面的colors
解析器主要有用。
colors:
inputs: /path/to/colors-file.txt
outputs:
templateName: swift5
output: Colors.swift
这将生成一个 enum ColorName
,文本文件中列出的每种颜色都有一个 static let
。
输入文件预期是:
- 一个纯文本文件,每行一个要注册的颜色,每行由要给颜色的名称组成,后跟":",然后是颜色的十六进制表示(如
rrggbb
或rrggbbaa
,可选前缀为#
或0x
)或文件中另一种颜色的名称。空白将被忽略。 - 一个JSON 文件,表示名称 -> 值的字典,每个值都是颜色的十六进制表示
- 一个XML 文件,预期格式与 Android colors.xml 文件相同,包含标签
<color name="AColorName">AColorHexRepresentation</color>
- 一个
*.clr
文件,由 Apple 的 Color Palettes 使用。
例如,你可以使用以下命令从系统色彩列表之一生成颜色:
colors:
inputs: ~/Library/Colors/MyColors.clr
outputs:
templateName: swift5
output: Colors.swift
生成的代码将与使用文本文件相同。
考虑到以下 colors.txt
文件:
Cyan-Color : 0xff66ccff
ArticleTitle : #33fe66
ArticleBody : 339666
ArticleFootnote : ff66ccff
Translucent : ffffffcc
生成的代码将如下所示:
internal struct ColorName {
internal let rgbaValue: UInt32
internal var color: Color { return Color(named: self) }
/// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#339666"></span>
/// Alpha: 100% <br/> (0x339666ff)
internal static let articleBody = ColorName(rgbaValue: 0x339666ff)
/// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#ff66cc"></span>
/// Alpha: 100% <br/> (0xff66ccff)
internal static let articleFootnote = ColorName(rgbaValue: 0xff66ccff)
...
}
使用示例
// 您可以使用便利构造函数这样创建颜色:
let title = UIColor(named: .articleBody) // iOS
let footnote = NSColor(named: .articleFootnote) // macOS
// 或者作为替代方案,您可以引用枚举实例并在其上调用 .color:
let sameTitle = ColorName.articleBody.color
let sameFootnote = ColorName.articleFootnote.color
这样,就不需要每次都输入颜色的红、绿、蓝、alpha值,也不需要为它们创建全局命名空间中的丑陋常量。
Core Data
coredata:
inputs: /path/to/model.xcdatamodeld
outputs:
templateName: swift5
output: CoreData.swift
这将解析指定的核心数据模型,为模型中的每个实体生成一个类,其中包含所有属性,并在需要时为关系和预定义的获取请求生成一些扩展。
内置模板生成的代码示例
internal class MainEntity: NSManagedObject {
internal class var entityName: String {
return "MainEntity"
}
internal class func entity(in managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
return NSEntityDescription.entity(forEntityName: entityName, in: managedObjectContext)
}
@nonobjc internal class func makeFetchRequest() -> NSFetchRequest<MainEntity> {
return NSFetchRequest<MainEntity>(entityName: entityName)
}
@NSManaged internal var attributedString: NSAttributedString?
@NSManaged internal var binaryData: Data?
@NSManaged internal var boolean: Bool
@NSManaged internal var date: Date?
@NSManaged internal var float: Float
@NSManaged internal var int64: Int64
internal var integerEnum: IntegerEnum {
get {
let key = "integerEnum"
willAccessValue(forKey: key)
defer { didAccessValue(forKey: key) }
guard let value = primitiveValue(forKey: key) as? IntegerEnum.RawValue,
let result = IntegerEnum(rawValue: value) else {
fatalError("Could not convert value for key '\(key)' to type 'IntegerEnum'")
}
return result
}
set {
let key = "integerEnum"
willChangeValue(forKey: key)
defer { didChangeValue(forKey: key) }
setPrimitiveValue(newValue.rawValue, forKey: key)
}
}
@NSManaged internal var manyToMany: Set<SecondaryEntity>
}
// MARK: Relationship ManyToMany
extension MainEntity {
@objc(addManyToManyObject:)
@NSManaged public func addToManyToMany(_ value: SecondaryEntity)
@objc(removeManyToManyObject:)
@NSManaged public func removeFromManyToMany(_ value: SecondaryEntity)
@objc(addManyToMany:)
@NSManaged public func addToManyToMany(_ values: Set<SecondaryEntity>)
@objc(removeManyToMany:)
@NSManaged public func removeFromManyToMany(_ values: Set<SecondaryEntity>)
}
使用示例
// 获取 MainEntity 的所有实例
let request = MainEntity.makeFetchRequest()
let mainItems = try myContext.execute(request)
// 类型安全的关系: 在这种情况下 `relatedItem` 将是 `SecondaryEntity?` 类型
let relatedItem = myMainItem.manyToMany.first
文件
files:
inputs: path/to/search
filter: .+\.mp4$
outputs:
templateName: structured-swift5
output: Files.swift
文件解析器旨在列出给定目录中文件和子目录的名称和MIME类型。这将使用给定的过滤器(默认 .*
)递归搜索指定的目录,为每个匹配的文件定义一个 struct File
,并定义一个表示文件目录结构的分层枚举。
内置模板生成的代码示例
internal enum Files {
/// test.txt
internal static let testTxt = File(name: "test", ext: "txt", path: "", mimeType: "text/plain")
/// subdir/
internal enum Subdir {
/// subdir/A Video With Spaces.mp4
internal static let aVideoWithSpacesMp4 = File(name: "A Video With Spaces", ext: "mp4", path: "subdir", mimeType: "video/mp4")
}
}
使用示例
// 使用 `url` 或 `path` 字段访问文件
let txt = Files.testTxt.url
let video = Files.Subdir.aVideoWithSpacesMp4.path
// 此外,还有 `url(locale:)` 和 `path(locale:)` 来指定区域设置
let localeTxt = Files.testTxt.url(locale: Locale.current)
let localeVideo = Files.Subdir.aVideoWithSpacesMp4.path(locale: Locale.current)
扁平结构支持
SwiftGen 还有一个模板,如果您对在生成的代码中保持文件夹结构不感兴趣。
扁平内置模板生成的代码示例
internal enum Files {
/// test.txt
internal static let testTxt = File(name: "test", ext: "txt", path: "", mimeType: "text/plain")
/// subdir/A Video With Spaces.mp4
internal static let aVideoWithSpacesMp4 = File(name: "A Video With Spaces", ext: "mp4", path: "subdir", mimeType: "video/mp4")
}
}
给定与上面相同的文件和文件夹结构,使用方式现在将是:
// 使用 `url` 或 `path` 字段访问文件
let txt = Files.testTxt.url
let video = Files.aVideoWithSpacesMp4.path
// 此外,还有 `url(locale:)` 和 `path(locale:)` 来指定区域设置
let localeTxt = Files.testTxt.url(locale: Locale.current)
let localeVideo = Files.aVideoWithSpacesMp4.path(locale: Locale.current)
字体
fonts:
inputs: /path/to/font/dir
outputs:
templateName: swift5
output: Fonts.swift
这将递归遍历指定的目录,查找任何字体文件(TTF、OTF等),为每个字体系列定义一个 struct FontFamily
,并在该系列下嵌套一个枚举,表示字体样式。
内置模板生成的代码示例
internal enum FontFamily {
internal enum SFNSDisplay: String, FontConvertible {
internal static let regular = FontConvertible(name: ".SFNSDisplay-Regular", family: ".SF NS Display", path: "SFNSDisplay-Regular.otf")
}
internal enum ZapfDingbats: String, FontConvertible {
internal static let regular = FontConvertible(name: "ZapfDingbatsITC", family: "Zapf Dingbats", path: "ZapfDingbats.ttf")
}
}
使用示例
// 您可以使用便利构造函数这样创建字体:
let displayRegular = UIFont(font: FontFamily.SFNSDisplay.regular, size: 20.0) // iOS
let dingbats = NSFont(font: FontFamily.ZapfDingbats.regular, size: 20.0) // macOS
// 或者作为替代方案,您可以引用枚举实例并在其上调用 .font:
let sameDisplayRegular = FontFamily.SFNSDisplay.regular.font(size: 20.0)
let sameDingbats = FontFamily.ZapfDingbats.regular.font(size: 20.0)
Interface Builder
ib:
inputs: /dir/to/search/for/storyboards
outputs:
- templateName: scenes-swift5
output: Storyboard Scenes.swift
- templateName: segues-swift5
output: Storyboard Segues.swift
这将为您的每个 NSStoryboard
/UIStoryboard
生成一个 enum
,分别为每个故事板场景或转场生成一个 static let
。
内置模板生成的代码示例
生成的代码将如下所示:
// 场景模板的输出
internal enum StoryboardScene {
internal enum Dependency: StoryboardType {
internal static let storyboardName = "Dependency"
internal static let dependent = SceneType<UIViewController>(storyboard: Dependency.self, identifier: "Dependent")
}
internal enum Message: StoryboardType {
internal static let storyboardName = "Message"
internal static let messagesList = SceneType<UITableViewController>(storyboard: Message.self, identifier: "MessagesList")
}
}
// 转场模板的输出
internal enum StoryboardSegue {
internal enum Message: String, SegueType {
case customBack = "CustomBack"
case embed = "Embed"
case nonCustom = "NonCustom"
case showNavCtrl = "Show-NavCtrl"
}
}
使用示例
// 您可以使用 `instantiate` 方法实例化场景:
let vc = StoryboardScene.Dependency.dependent.instantiate()
// 您可以执行转场:
vc.perform(segue: StoryboardSegue.Message.embed)
// 或匹配它们(在 prepareForSegue 中):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch StoryboardSegue.Message(segue) {
case .embed?:
// 为您的自定义转场准备,将信息传递给目标 VC
case .customBack?:
// 为您的自定义转场准备,将信息传递给目标 VC
default:
// 来自其他场景的其他转场,不由此 VC 处理
break
}
}
JSON 和 YAML
json:
inputs: /path/to/json/dir-or-file
outputs:
templateName: runtime-swift5
output: JSON.swift
yaml:
inputs: /path/to/yaml/dir-or-file
outputs:
templateName: inline-swift5
output: YAML.swift
这将解析给定的文件,或者在给定目录时,递归搜索 JSON 和 YAML 文件。它将为每个文件(以及需要时文件中的文档)定义一个 enum
,并为文件内容定义类型安全的常量。
与其他解析器不同,这个解析器旨在允许您使用更多自定义输入(因为这些格式相当开放,可以满足您的需求)来生成代码。这意味着对于这些解析器(以及 plist
解析器),您可能更有可能使用自定义模板来生成适当适应/调整到您的输入的代码,而不是使用内置模板。要了解更多关于编写自己的自定义模板的信息,请参阅专门的文档。
内置模板生成的代码示例
```swift internal enum JSONFiles { internal enum Info { private static let _document = JSONDocument(path: "info.json") internal static let key1: String = _document["key1"] internal static let key2: String = _document["key2"] internal static let key3: [String: Any] = _document["key3"] } internal enum Sequence { internal static let items: [Int] = objectFromJSON(at: "sequence.json") } } ```使用示例
// 这将是一个字典
let foo = JSONFiles.Info.key3
// 这将是一个 [Int] 数组
let bar = JSONFiles.Sequence.items
Plist 文件
plist:
inputs: /path/to/plist/dir-or-file
outputs:
templateName: runtime-swift5
output: Plist.swift
这将解析给定的文件,或者当给定一个目录时,递归搜索 Plist 文件。它将为每个文件(以及文件中需要的文档)定义一个 enum
,并为文件内容定义类型安全的常量。
与其他解析器不同,这个解析器旨在允许您使用更多自定义输入(因为格式相当开放以满足您的需求)来生成代码。这意味着对于这个解析器(以及 json
和 yaml
解析器),您可能更倾向于使用自定义模板来生成适当调整/适应您的输入的代码,而不是使用捆绑的模板。要了解更多关于编写自定义模板的信息,请参阅专门的文档。
捆绑模板生成的代码示例
internal enum PlistFiles {
internal enum Test {
internal static let items: [String] = arrayFromPlist(at: "array.plist")
}
internal enum Stuff {
private static let _document = PlistDocument(path: "dictionary.plist")
internal static let key1: Int = _document["key1"]
internal static let key2: [String: Any] = _document["key2"]
}
}
使用示例
// 这将是一个数组
let foo = PlistFiles.Test.items
// 这将是一个 Int
let bar = PlistFiles.Stuff.key1
字符串
strings:
inputs: /path/to/language.lproj
outputs:
templateName: structured-swift5
output: Strings.swift
这将生成一个 Swift enum L10n
,它将所有 Localizable.strings
和 Localizable.stringsdict
(或其他表)的键映射到 static let
常量。如果检测到像 %@
、%d
、%f
这样的占位符,它将生成一个具有适当参数类型的 static func
,以提供类型安全的格式化。默认情况下,它将使用字符串文件中的注释(如果存在)或字符串的默认翻译,为生成的常量和函数添加注释。
请注意,键名中的所有点都会转换为代码中的点(通过使用嵌套枚举)。您可以使用解析器选项提供一个不同于
.
的分隔符来将键名拆分为子结构 - 参见解析器文档。
结构化捆绑模板生成的代码示例
给定以下 Localizable.strings
文件:
/* 警告标题 */
"alert_title" = "警告标题";
"alert_message" = "一些警告内容";
/* 上面没有空格的注释 */
"bananas.owner" = "这 %d 根香蕉属于 %@。";
以及以下 Localizable.stringsdict
文件:
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>apples.count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@apples@</string>
<key>apples</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>你没有苹果</string>
<key>one</key>
<string>你有一个苹果</string>
<key>other</key>
<string>你有 %d 个苹果。哇,真多!</string>
</dict>
</dict>
</dict>
</plist>
提醒:不要忘记在
*.strings
文件中的每一行末尾都加上分号;
!现在在 Swift 代码中我们不需要分号,很容易忘记Localizable.strings
文件格式仍然需要它 😉
生成的代码将包含以下内容:
internal enum L10n {
/// 一些警告内容
internal static let alertMessage = L10n.tr("Localizable", "alert__message", fallback: #"一些警告内容"#)
/// 警告标题
internal static let alertTitle = L10n.tr("Localizable", "alert__title", fallback: #"警告标题"#)
internal enum Apples {
/// 你有 %d 个苹果
internal static func count(_ p1: Int) -> String {
return L10n.tr("Localizable", "apples.count", p1, fallback: #"你有 %d 个苹果"#)
}
}
internal enum Bananas {
/// 上面没有空格的注释
internal static func owner(_ p1: Int, _ p2: Any) -> String {
return L10n.tr("Localizable", "bananas.owner", p1, String(describing: p2), fallback: #"这 %d 根香蕉属于 %@。"#)
}
}
}
请注意,如果相同的键同时出现在 .strings
和 .stringsdict
文件中,SwiftGen 将只考虑 .stringsdict
文件中的内容,因为这也是 Foundation 在运行时的行为。
使用示例
一旦脚本生成了代码,您可以在 Swift 代码中这样使用它:
// 简单字符串
let message = L10n.alertMessage
let title = L10n.alertTitle
// 带参数,注意每个参数都需要是正确的类型
let apples = L10n.Apples.count(3)
let bananas = L10n.Bananas.owner(5, "Olivier")
平面字符串支持
SwiftGen 还有一个模板来支持平面字符串文件(即不使用"点语法"将键拆分为子结构)。优点是您的键不会以任何方式被修改;缺点是自动完成不会那么好。
平面捆绑模板生成的代码示例
internal enum L10n {
/// 一些警告内容
internal static let alertMessage = L10n.tr("Localizable", "alert__message", fallback: #"一些警告内容"#)
/// 警告标题
internal static let alertTitle = L10n.tr("Localizable", "alert__title", fallback: #"警告标题"#)
/// 你有 %d 个苹果
internal static func applesCount(_ p1: Int) -> String {
return L10n.tr("Localizable", "apples.count", p1, fallback: #"你有 %d 个苹果"#)
}
/// 上面没有空格的注释
internal static func bananasOwner(_ p1: Int, _ p2: Any) -> String {
return L10n.tr("Localizable", "bananas.owner", p1, String(describing: p2), fallback: #"这 %d 根香蕉属于 %@。"#)
}
}
给定与上面相同的 Localizable.strings
和 Localizable.stringsdict
,使用方式现在将是:
// 简单字符串
let message = L10n.alertMessage
let title = L10n.alertTitle
// 带参数,注意每个参数都需要是正确的类型
let apples = L10n.applesCount(3)
let bananas = L10n.bananasOwner(5, "Olivier")
许可证
此代码和工具采用 MIT 许可证。请参阅此存储库中的 LICENCE
文件。
归属
此工具由以下技术支持
- Stencil 和 Kyle Fuller 的其他几个库
- SwiftGenKit 和 StencilSwiftKit,我们在 SwiftGen 的内部框架
目前主要由 @AliSoftware 和 @djbe 维护。但我无法充分感谢所有其他贡献者,他们在不同版本中帮助使 SwiftGen 变得出色! 🎉
如果您想贡献,不要犹豫,打开一个 Pull Request,甚至加入团队!
其他库/工具
如果您想摆脱不仅是资源的基于字符串的 API,还包括 UITableViewCell
、UICollectionViewCell
和基于 XIB 的视图,您应该看看我的 Mixin Reusable。
如果您想从自己的 Swift 代码生成 Swift 代码(太元了!),比如为您的类型生成 Equatable
一致性和许多其他类似的事情,请使用 Sourcery。
SwiftGen 和 Sourcery 是互补工具。事实上,Sourcery 也使用 Stencil
,以及 SwiftGen 的 StencilSwiftKit
,所以您可以为两者使用完全相同的模板语法!
您也可以在 Twitter 上关注我,了解我正在创建的其他项目的新闻/更新,或者阅读我的博客。