Skip to content

Swift Property Wrapper

Posted on:September 18, 2020 at 11:55 AM

Property Wrapper

刚接触SwiftUI时, 看到这些东西

@State, @Binding, @Published, @ObservedObject, @Environment

是不是一脸懵逼, 但是用起来的时候却额外的丝滑, 比如当我们想要动态的更新View的布局时可以这么写:

struct ContentView: View {
    // 1
    @State var isSearching: Bool = false

    var body: some View {
        // 2
        if isSearching {
            Text("Hello, world!")
                .padding()
        } else {
            Button("Search") {
                // 3
                isSearching = true
            }
        }
    }
}

是不是非常丝滑? 我很长一段时间都把这些东西当作关键字来理解, 因为我只需要知道他们各自的作用就好了, 直到我看到官方的文章才了解什么是

Property Wrapper 🌚, 其他的Property Wrapper这里不在举例, 我们重点讨论Property Wrapper是什么.

官方给出的解释是: 有一些属性实现模式会重复出现, 比如lazy, @NSCopying, 但他们不想每次都用硬编码的模式将这个关键字添加到编译器中, 所以提出了Property Wrapper机制.

比如下面懒加载的实现:

class Person {
    lazy var name: String = "Mas0n"
}

等同于

class Person {
    private var _name: String?
    var name: String {
        get {
            if let value = _name { return value }
            let initialValue = "Mas0n"
            _name = initialValue
            return initialValue
        }
        set {
            _name = newValue
        }
    }
}

苹果之前将lazy放到了Swift的语言中, 这样有很多缺点, 比如会让Swift和编译器变得复杂, 并且缺少灵活性, 比如当有更多的像lazy这样的变种实现, 他们不想把这些硬编码全都放到Swift中, 比如想要实现一个可以重制lazy属性的lazyReset.

因此Apple提出了Property Wrapper的解决方案:

我可以自己实现一个lazy关键字通过Property Wrapper

// 1
@propertyWrapper
enum Lazy<Value> {
    case uninitialized(() -> Value)
    case initialized(Value)

    // 2
    init(wrappedValue: @autoclosure @escaping () -> Value) {
        self = .uninitialized(wrappedValue)
    }

    var wrappedValue: Value {
        mutating get {
            // 3
            switch self {
            case .uninitialized(let initializer):
                let value = initializer()
                self = .initialized(value)
                return value
            case .initialized(let value):
                return value
            }
        }
        set {
            // 4
            self = .initialized(newValue)
        }
    }
}

使用:

@Lazy var name = "Mas0n"
// or
@Lazy(wrappedValue: "Mas0n") var name

这样我们自定义实现的Property Wrapper就完成了, 它的效果等同于Swift语言中的lazy, 是不是很方便呢? 这样当我们想要再次实现一个类似于lazy的关键字的时候, 就不必再去为Swift添加硬编码了.

Property Wrapper还提供了可选的projectedValue属性, 类似于wrappedValue, 当我们实现了projectedValue时, 我们可以通过$在外部快速的访问一个projectedValue

所以就诞生了一开始SwiftUI中的@State, @Binding, @Published, @ObservedObject, @Environment, 他们本质山都是Apple用Property Wrapper定义的struct/enum/class

我们可以推测一下@State的实现:

@propertyWrapper
struct State<Value>: DynamicViewContent {
    init(wrappedValue: Value) { }
    var wrappedValue: Value { }
    var projectedValue: Binding<Value> { }
}

用法:

@State var name: String
// 1
_name = State(wrappedValue: "Mas0n")
// 2
name = "Mas0n"
// 3
$name.wrappedValue = "Mas0n"

总结

其实Property Wrapper的作用就是可以帮助我们封装模版代码, 简化重复的代码, 并且提供了wrappedValueprojectedValue方便我们访问自定义的getset实现.

以上文章参考苹果官方的资料SE-0258

更详细的内容可以去链接里查看