13

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

@WrapperOne @WrapperTwo var foo: T

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

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

@DelayedMutable @Copying var path: UIBezierPath

在这里,我们有一个属性,我们可以将初始化延迟到以后。当我们确实设置了一个值时,它将通过NSCopying的复制方法进行复制。组合是通过将后面的包装器类型嵌套在较早的包装器类型中来实现的,其中最内层的嵌套类型是原始属性的类型。对于上面的示例,后备存储将是类型 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 }
}

请注意,这种设计意味着属性包装器组合不是可交换的,因为属性的顺序会影响嵌套的执行方式: @DelayedMutable @Copying var path1: UIBezierPath // _path1 的类型为 DelayedMutable> @Copying @DelayedMutable var path2: UIBezierPath / /error: _path2 has ill-formed type 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
    }
}

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

这样就可以实现:

@Tripled @Doubled var number = 5

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

任何帮助将不胜感激。

4

2 回答 2

6

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

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

例如,在 Doubled 的情况下:

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

你可以对 Tripled 做同样的事情,使用 Tripleable 协议,等等。

但是,我应该注意,不是嵌套@Tripled @Doubled,而是创建另一个包装器可能会更好@Multiple(6):这样您就不必处理任何协议,但您会得到相同的效果。

于 2020-05-10T22:24:01.453 回答
3

使用多个属性包装器存在一些问题,因此部分功能已从 Swift 5.1 中移除,但它将在 5.2 中可用。在那之前,您不能像这样直接使用多个属性包装器。

于 2020-01-14T21:38:57.683 回答