Project Icon

SwiftGen

自动生成类型安全Swift资源代码的工具

SwiftGen是一个Swift代码生成工具,可为项目资源文件自动创建类型安全的代码。它支持图片、本地化字符串等多种资源类型,并通过Stencil模板实现自定义。SwiftGen帮助开发者避免拼写错误,提供自动补全,在编译时捕获资源名称问题,从而提升代码质量和开发效率。该工具可根据不同项目需求进行灵活配置。

SwiftGen

CocoaPods 兼容 平台 Swift 4.x Swift 5.x

SwiftGen 是一个工具,用于自动为项目资源(如图像、本地化字符串等)生成 Swift 代码,使其在使用时具有类型安全性。

使用这个工具有多个好处:

  • 避免使用字符串时出现任何拼写错误的风险
  • 免费的自动补全
  • 避免使用不存在的资产名称的风险
  • 所有这些都将由编译器确保,从而避免运行时崩溃的风险。

此外,由于使用 Stencil 模板,它是完全可定制的,因此即使它附带预定义的模板,你也可以创建自己的模板来生成符合你的需求和指南的任何代码!

安装

根据你的偏好和需求,有多种方式可以在你的机器或项目中安装 SwiftGen:

下载 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/5structured-swift4/5 模板等。

你可以在这里的仓库中找到每个内置模板的文档这里,文档按每个 SwiftGen 解析器组织为一个文件夹,然后每个模板一个 MarkDown 文件。你还可以使用 swiftgen template doc 直接从终端在浏览器中打开该文档页面。

每个 MarkDown 文件记录了它针对的 Swift 版本,该模板的用例(在哪些情况下你可能会选择该模板而不是其他模板),调用时可用的自定义参数(使用配置文件中的 params: 键),以及一些代码示例。

不要犹豫,提出 PR 分享你对内置模板的改进建议 😉

其他文档

Playground

本仓库中提供的 SwiftGen.playground 允许你使用该工具通常生成的代码进行试验,并查看一些如何利用它的示例。

这允许你快速了解 SwiftGen 生成的典型代码的样子,以及如何在代码中使用生成的常量。

Markdown 专用文档

本仓库以及相关的 StencilSwiftKit 仓库中有大量以 Markdown 格式提供的文档。

请务必查看每个仓库的 "Documentation" 文件夹

特别是,除了前面提到的迁移指南配置文件文档外,SwiftGen 仓库的 Documentation/ 文件夹还包括:

教程

你还可以在互联网上找到其他帮助和教程材料,比如我在 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

输入文件预期是:

  • 一个纯文本文件,每行一个要注册的颜色,每行由要给颜色的名称组成,后跟":",然后是颜色的十六进制表示(如 rrggbbrrggbbaa,可选前缀为 #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,并为文件内容定义类型安全的常量。

与其他解析器不同,这个解析器旨在允许您使用更多自定义输入(因为格式相当开放以满足您的需求)来生成代码。这意味着对于这个解析器(以及 jsonyaml 解析器),您可能更倾向于使用自定义模板来生成适当调整/适应您的输入的代码,而不是使用捆绑的模板。要了解更多关于编写自定义模板的信息,请参阅专门的文档

捆绑模板生成的代码示例
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.stringsLocalizable.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.stringsLocalizable.stringsdict,使用方式现在将是:

// 简单字符串
let message = L10n.alertMessage
let title = L10n.alertTitle

// 带参数,注意每个参数都需要是正确的类型
let apples = L10n.applesCount(3)
let bananas = L10n.bananasOwner(5, "Olivier")

许可证

此代码和工具采用 MIT 许可证。请参阅此存储库中的 LICENCE 文件。

归属

此工具由以下技术支持

目前主要由 @AliSoftware@djbe 维护。但我无法充分感谢所有其他贡献者,他们在不同版本中帮助使 SwiftGen 变得出色! 🎉

如果您想贡献,不要犹豫,打开一个 Pull Request,甚至加入团队!

其他库/工具

如果您想摆脱不仅是资源的基于字符串的 API,还包括 UITableViewCellUICollectionViewCell 和基于 XIB 的视图,您应该看看我的 Mixin Reusable

如果您想从自己的 Swift 代码生成 Swift 代码(太元了!),比如为您的类型生成 Equatable 一致性和许多其他类似的事情,请使用 Sourcery

SwiftGen 和 Sourcery 是互补工具。事实上,Sourcery 也使用 Stencil,以及 SwiftGen 的 StencilSwiftKit,所以您可以为两者使用完全相同的模板语法!

您也可以在 Twitter 上关注我,了解我正在创建的其他项目的新闻/更新,或者阅读我的博客

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号