如何编写 swift 属性包装器?

Ben*_*Ben 11 swift property-wrapper

我最近一直在试验 swift 属性包装器,想知道是否有任何方法可以将它们组合在一起以实现更加模块化的架构。例如:

@WrapperOne @WrapperTwo var foo: T
Run Code Online (Sandbox Code Playgroud)

查看文档一无所获。关于如何做到这一点的唯一参考是在这个 GitHub 页面(https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md)(下面的引用),这似乎在说有可能的。其他文章说他们很难撰写,但没有解释如何去做。但是,我无法理解它,如果有人可以向我展示一些关于如何实现它的示例代码(见帖子底部),我将不胜感激。

当为给定属性提供多个属性包装器时,包装器组合在一起以获得两种效果。例如,考虑的组合物DelayedMutableCopying

@DelayedMutable @Copying var path: UIBezierPath
Run Code Online (Sandbox Code Playgroud)

在这里,我们有一个可以延迟初始化的属性。当我们确实设置了一个值时,它将通过NSCopying的 copy 方法进行复制。组合是通过将较晚的包装类型嵌套在较早的包装类型中来实现的,其中最内层的嵌套类型是原始属性的类型。对于上面的示例,后备存储将是类型 DelayedMutable<Copying<UIBezierPath>> ,路径的合成 getter/setter 将查看 .wrappedValue 的两个级别:

private var _path: DelayedMutable<Copying<UIBezierPath>> = .init()
var path: UIBezierPath {
    get { return _path.wrappedValue.wrappedValue }
    set { _path.wrappedValue.wrappedValue = newValue }
}
Run Code Online (Sandbox Code Playgroud)

请注意,这种设计意味着属性包装器组合不是可交换的,因为属性的顺序会影响嵌套的执行方式:@DelayedMutable @Copying var path1: UIBezierPath // _path1 has type DelayedMutable> @Copying @DelayedMutable var path2: UIBezierPath / / 错误:_path2 有格式错误的类型 Copying> 在这种情况下,类型检查器会阻止第二次排序,因为DelayedMutable不符合NSCopying协议。情况并非总是如此:一些语义错误的组合不一定会被类型系统捕获。这种组合方法的替代方案在“考虑的替代方案”中介绍。

理想情况下,我想实现如下内容:

@propertyWrapper
struct Doubled {
    var number: Int
    var wrappedValue: Int {
        get { (value * 2) }
        set { value = Int(newValue / 2) }
    }
    init(wrappedValue: Int) {
        self.number = wrappedValue
    }
}
Run Code Online (Sandbox Code Playgroud)

@propertyWrapper
struct Tripled {
    var number: Int
    var wrappedValue: Int {
        get { (value * 3) }
        set { value = Int(newValue / 3) }
    }
    init(wrappedValue: Int) {
        self.number = wrappedValue
    }
}
Run Code Online (Sandbox Code Playgroud)

以便可以实现:

@Tripled @Doubled var number = 5
Run Code Online (Sandbox Code Playgroud)

我知道这个例子是实现属性包装器组合的一个有点愚蠢的理由,但这仅仅是为了学习新功能时的简单起见。

任何帮助将不胜感激。

Noa*_*ore 5

从 Swift 5.2 开始,嵌套的属性包装器变得更加稳定,但它们仍然有点难以使用。我在这里写了一篇关于它的文章,但诀窍是,由于外部包装器wrappedValue是内部包装器的类型,而内部包装器wrappedValue是直接属性类型,您必须使包装器对这两种类型进行操作。

我遵循的基本思想是创建一个包装器操作的协议。然后,您可以让其他包装器也符合该协议,以启用嵌套。

例如,在双倍的情况下:

protocol Doublable {
    func doubling() -> Self
    func halving() -> Self
}

@propertyWrapper
struct Doubled<T: Doublable> {
    var number: T
    var wrappedValue: T {
        get { number.doubling() }
        set { number = newValue.halving() }
    }
    init(wrappedValue: T) {
        self.number = wrappedValue
    }
}

extension Int: Doublable {
    func doubling() -> Int {
        return self * 2
    }

    func halving() -> Int {
        return Int(self / 2)
    }
}

extension Doubled: Doublable {
    func doubling() -> Self {
        return Doubled(wrappedValue: self.wrappedValue)
    }

    func halving() -> Self {
        return Doubled(wrappedValue: self.wrappedValue)
    }
}

struct Test {
    @Doubled @Doubled var value: Int = 10
}

var test = Test()
print(test.value) // prints 40
Run Code Online (Sandbox Code Playgroud)

您可以使用 Tripleable 协议等为 Tripled 做同样的事情。

但是,我应该注意,不是 nesting @Tripled @Doubled,最好创建另一个包装器,例如@Multiple(6):这样您就不必处理任何协议,但您将获得相同的效果。