@State实现原理以及自我实现@State功能
用过SwiftUI开发页面的同学,对@State的使用非常常见,它是用来监控属性值的变化,并通知SwiftUI更新页面的。
之前的文章中我已经介绍过@propertyWrapper的简单运用,不知道的同学,可以点击查看。
结合@propertyWrapper的运用,我们可以推断@State的实现原理,其实Swift中有个类似于RxSwift框架的东西,叫:Combine,用过RxSwift都知道,它提供了一整套用于将事件调用转成数据流的形式进行订阅等相关操作。
这样我们就不难想想@State的实现原理了,其实就是在属性包装器内部实现了一套Publisher机制,然后SwiftUI对这些属性包装器进行订阅,观察属性值的变化,当值发生了变化,就会更新UI界面。
下面我们通过一个demo来讲解一下其实现原理,话不多说,直接上代码
@propertyWrapper struct DrPropertyWrapper<T>{ private let wapper: DrValueWrapper<T> // 这里是被包装的属性值的包装类 var wrappedValue: T { get{ wapper.value } nonmutating set{ wapper.value = newValue } } init(wrappedValue: T){ wapper = DrValueWrapper(wrappedValue) } } final class DrValueWrapper<T>{ var value: T init(_ val: T) { value = val } }
这是我之前写的一篇文章中实现的自定义属性包装器,接下来我们让这个属性包装器类实现@State的效果。
首先我们需要让DrValueWrapper实现ObservableObject协议,让其拥有一个Publisher对象,该协议提供了一个objectWillChange: ObservableObjectPublisher属性,用于发送更新通知。
final class DrValueWrapper<T>: ObservableObject{ var value: T { willSet{ // 属性值将要发生了改变 objectWillChange.send() } } init(_ val: T) { value = val } }
接下来调整我们的属性包装器,先看代码
@propertyWrapper struct DrPropertyWrapper<T>{ // 这里我们增加了@ObservedObject注解,目的是订阅这个属性的发布者 @ObservedObject private var wapper: DrValueWrapper<T> // 这里是被包装的属性值的包装类 }
可以看出,我们在 wapper 属性前增加了一个@ObservedObject属性包装器注解,这个包装器会订阅实现ObservableObject协议的对象,并且该属性包装器只能修饰var的可变属性,所以我们将上面的let改成了var,该属性包装器是由SwiftUI框提供的,可见SwiftUI页面的更新就跟该属性包装器对象有关。
import SwiftUI struct ContentView: View { // 这里使用我们自己的属性包装器注解 @DrPropertyWrapper private var password: String = "" var body: some View { VStack { Text("Result: \(password)") Button("更新密码"){ password = ["123", "456", "789"].randomElement()! }.frame(width: 200, height: 40, alignment: .center) .background(Color.blue) .padding(.top, 50) } } }
此刻运行程序,我们点击更新密码按钮,我们没有看到界面有任何变化,我们经过上面的一系列操作,我们实现了将一个属性包装起来,并且可以知道其值的变化,ObservedObject是SwiftUI提供的,它订阅了值的变化,应该也实现了更新页面的操作。但是它是如何跟UI页面关联的呢?所以这个才是关键。
于是带着疑问,通过查看Sate这个包装器类,看到了它实现了DynamicProperty协议,我们也让自己的包装器实现这个协议试试
@propertyWrapper struct DrPropertyWrapper<T>: DynamicProperty{ }
我们再次运行程序,点击更新密码按钮,成功了,页面刷新了!!!
看来实现属性值改变,页面刷新的另一个关键是这个包装器对象必须实现DynamicProperty协议,具体之间的关联暂不清楚,有知道的同学,可以告诉我,谢谢!!!