Swift:自动处理属性包装器的默认值的教程

344 阅读2分钟

Swift的属性包装器功能使我们能够封装一个给定的属性值,以便在该值被创建或改变时运行自定义逻辑。例如,下面这个Capitalized 类型(借用了我关于属性包装器的长篇文章)让我们自动将分配给任何一个被包装的属性的所有String 值大写。:

@propertyWrapper struct Capitalized {
    var wrappedValue: String {
        didSet { wrappedValue = wrappedValue.capitalized }
    }

    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue.capitalized
    }
}

属性包装器的一个非常好的方面是,编译器会自动将我们在调用站点定义的任何默认值映射到上述init(wrappedValue:) 初始化器中--这意味着我们可以像通常那样继续定义默认值,即使一个属性被我们的一个自定义类型包装了:

struct Document {
    @Capitalized var name = "Untitled document"
}

这种自动默认值映射甚至延伸到了接受额外初始化器参数的属性包装器上。例如,在这里我们定义了一个属性包装器,它允许我们使用一个命令行参数来覆盖一个给定的值,只需将我们的默认值参数命名为wrappedValue (并将其放在初始化器参数列表的首位),我们就可以继续为被包装的属性分配默认值:

@propertyWrapper struct CommandLineOverridable {
    let wrappedValue: Bool
    var flagName: String

    private let defaults = UserDefaults.standard

    init(wrappedValue defaultValue: Bool, flagName: String) {
        self.flagName = flagName

        #if DEBUG
        // First, we check if a command line argument matching
        // our flagName even exists, before retrieving a Bool
        // value for it, since otherwise we'd get 'false' back
        // for flags that weren't passed at all:
        if defaults.object(forKey: flagName) != nil {
            wrappedValue = defaults.bool(forKey: flagName)
            return
        }
        #endif

        wrappedValue = defaultValue
    }
}

注意我们是如何使用Foundation的UserDefaults API来解析上面的命令行参数的。

有了上述内容,我们现在只需告诉每个CommandLineOverridable 实例我们想对每个给定的属性使用什么标志,编译器会自动将该属性的默认值映射到我们的wrappedValue 初始化参数:

struct AppConfiguration {
    @CommandLineOverridable(flagName: "sync")
    static var enableSync = true
    @CommandLineOverridable(flagName: "rememberLogin")
    static var rememberLogin = false
}

谢谢你的阅读!