SVMPrefs

Code Coverage: 95% CI Workflow

注意:此工具需要 Xcode 11 才能编译,因为它使用了一些 Swift 5.1 语言特性。

SVMPrefs 是一个命令行工具,用于生成基于 SVM 数据读取和写入偏好设置的代码。

SVM 名称来自三个主要数据元素:Store(存储),Variable(变量)和 Migrate(迁移)。

为什么?

通常使用 UserDefaults 的方式如下所示。

let prefs = UserDefaults.standard
if !prefs.bool(forKey: "firstLaunch") {
    prefs.set(true, forKey: "firstLaunch") {
    showFTUX()
}

这种临时使用 UserDefaults 的方式至少存在 11 个问题

  1. 调用者必须知道数据源:UserDefaults.standard
  2. 调用者必须引用键名两次:"firstLaunch"
  3. 调用者必须知道类型:prefs.booltrue
  4. 调用者必须了解任何转换:!true(你注意到反转的逻辑了吗?)
  5. 所有这些代码,在使用点,都增加了代码实际目的周围的噪音 -- 在首次启动应用程序时调用 showFTUX()
  6. 此代码然后在其他地方重复用于某些偏好设置,从而违反了 DRY 原则
  7. 使用上述代码不易进行单元测试。
  8. 代码中可能存在许多其他偏好设置 -- 很可能没有文档
  9. 将偏好设置迁移到不同的 UserDefaults 位置并非易事
  10. 移除已弃用的偏好设置很容易被遗忘或忽略
  11. 此方法提供的代码补全帮助有限

上述问题的一种解决方案是定义一个专用类,该类封装每个偏好设置的详细信息,以便应用程序逻辑可以专注于以简单明了的方式使用它们。使用专用类,使用偏好设置可以像这样

let prefs = AppPrefs()
if prefs.isFirstLaunch {
    prefs.isFirstLaunch = false
    showFTUX()
}

SVMPrefs 通过根据您的 SVM 规范生成读取、写入、迁移和删除偏好设置的代码,更进一步。

从本地构建安装

注意:此工具需要 Xcode 11 才能编译,因为它使用了一些 Swift 5.1 语言特性。

从 SVMPrefs 根目录运行 make install 以构建并在 /usr/local/bin 中安装 svmprefs 二进制文件。

您可以使用 Xcode 11 通过打开 Package.swift 文件或从命令行使用 xed . 打开项目。

命令行

基本命令行如下:svmprefs [command] [options] [args]

命令 描述
help 显示帮助文本
version 显示版本信息
gen source_file_name 处理给定的文件并在 SVM 数据中生成代码

您可以运行 svmprefs gen --help 以获取有关 gen 命令的更多详细信息。

> svmprefs gen --help

Usage: svmprefs gen <input> [options]

Processes the given file and generates the code for the contained SVM data

Options:
  -b, --backup            Create a backup copy of the source file (foo.m -> foo.backup.m)
  -d, --debug             Print debug output
  -h, --help              Show help information
  -i, --indent <value>    Set indent width. (Default: 4)

在 Xcode 中使用

您可以将 SVMPrefs 集成到您的 Xcode 项目中,使其在编译之前生成代码,并通过运行脚本突出显示 SVM 规范中的任何错误。

添加一个新的“Run Script Phase”,该阶段发生在编译之前,内容如下所示。

set -e

if which svmprefs >/dev/null; then
  # Update this section with the desired command line options and
  # actual file paths to your code that have SVM data.
  # NOTE: svmprefs supports just one file at a time.
  svmprefs gen -i 4 $SRCROOT/Common/SharedUserDefaults.swift
else
  echo "WARNING: svmprefs is not installed. See: https://github.com/ghv/SVMPrefs"
fi

SVM 数据格式

您必须在代码中添加一个注释块,该注释块以 SVMPREFS 开始和结束,如下所示。

/*SVMPREFS [NB: the rest of this line reserved for svmprefs tool use]

# this line is treated as a comment by svmprefs
S demo
V Bool | isDemo | demo_key_name | |

SVMPREFS*/

# — 注释

任何以 # 作为第一个非空白字符的行都被视为 SVMPREFS 注释块中的注释

S — Store(存储)

存储记录有三个由 | 分隔的参数

enum Options: String {
    case generateRemoveAllMethod = "RALL"
}

您为每个唯一的套件定义一个 S 记录。每个 S 记录后跟任意数量的 V 记录。

V — Variable(变量)

变量记录有五个由 | 分隔的参数

enum Options: String {
    case generateInvertedLogic = "INV"
    case decorateWithObjC = "OBJC"

    // Defining a Bool named 'firstLaunch' with this option will
    // generate code for it as 'isFirstLaunch' in some places.
    case decorateNameWithIsPrefix = "IS"

    case omitGeneratingGetterAndSetter = "NVAR"
    case omitGeneratingSetter = "NSET"

    case addToRemoveAllMethod = "RALL"
    // Use this to omit when RALL is set at the store level:
    case omitFromRemoveAllMethod = "NRALL"

    case generateRemovePrefMethod = "REM"
    case generateIsSetMethod = "ISSET"
}

M — Migrate(迁移)

如果您有一个或多个 S 记录,则可以使用 M 记录将偏好设置从一个存储移动到另一个存储,或者根据需要删除它们。

迁移记录有四个由 | 分隔的参数

该工具会将所有迁移代码插入到一个名为 migrate() 的函数中,您应该在每次应用程序启动时调用该函数。迁移是作为对象到对象的读取和写入执行的。迁移后,该键将从源存储中删除。您必须在您的类中的某个位置包含一个名为 migrate 的代码标记。

任何被迁移或删除的变量都将无法再从源存储作为属性访问。但是,此属性的键将保留在源存储的 Keys 枚举中。

生成的代码标记

生成的代码必须放置在源文件中的一个类中。如下所示添加一对注释,其中 identifier 是存储的 name,以指示要插入生成代码的位置。

    // MARK: BEGIN identifier
    // MARK: END identifier

如果您定义了任何迁移,您还需要包含一个用于 migrate identifier 的代码标记。

您可以在同一个 MARK 中指定多个 identifier,方法是将它们与逗号分隔符连接在一起,按照您希望它们出现的顺序排列。开始和结束标记必须具有相同标识符且顺序相同。逗号周围没有空格,否则您将收到错误。

    // MARK: BEGIN foo,bar
    // MARK: END foo,bar

最小的 Swift 示例

/*SVMPREFS
S demo
V Bool | isDemo | demo_key_name | |
SVMPREFS*/

class MyDemoPreferences {

    // ANYTHING HERE IS LEFT UNTOUCHED

    // MARK: BEGIN demo
    // ANYTHING HERE WILL BE REPLACED
    // BY THE GENERATED CODE
    // MARK: END demo

    // ANYTHING HERE IS LEFT UNTOUCHED
}

问题与技巧

如何注入要读取或写入的偏好设置?

在某些情况下,您可能需要在函数中提供对偏好设置的引用,以便其中的代码可以读取或写入该偏好设置,而不必知道实际的属性。如果您过去使用键名作为此引用,则现在可以使用 KeyPathWriteableKeyPath 来实现此目的。这是一个例子。

/*SVMPREFS
S keypath
V [String] | primaryList   | app.primaryList   | |
V [String] | secondaryList | app.secondaryList | |
SVMPREFS*/

class KeyPathPrefs {
    // MARK: BEGIN keypath
    // MARK: END keypath
}

// Somewhere in your app...
func demo() {
    // A function that needs to use one of several preferences
    func processList(keyPath: KeyPath<KeyPathPrefs, [String]>) {
        let prefs = KeyPathPrefs()
        let list = prefs[keyPath: keyPath]
        // Do something with this list...
    }

    // How you call it:
    processList(keyPath: \.primaryList)
    processList(keyPath: \.secondaryList)
}

许可证

版权所有 2019-2020 The SVMPrefs Authors。SVMPrefs 在 Apache 2.0 许可证下获得许可。欢迎贡献。

有关许可证信息,请参阅 LICENSE.md

有关 The SVMPrefs Authors 的信息,请参阅 CONTRIBUTORS.md

有关依赖项许可证信息,请参阅 NOTICE.md

感谢您!