大家应该都知道SwiftUI的设计理念是Data flow, 也就是View是由数据驱动的,我们把View依赖的这些数据称之为状态,因此,SwiftUI中的数据管理就是状态管理。
常见的状态管理由以下几个:
[AppStorage](https://link.zhihu.com/?target=https%3A//developer.apple.com/documentation/swiftui/appstorage)
[Binding](https://link.zhihu.com/?target=https%3A//developer.apple.com/documentation/swiftui/binding)
[Environment](https://link.zhihu.com/?target=https%3A//developer.apple.com/documentation/swiftui/environment)
[EnvironmentObject](https://link.zhihu.com/?target=https%3A//developer.apple.com/documentation/swiftui/environmentobject)
[FetchRequest](https://link.zhihu.com/?target=https%3A//developer.apple.com/documentation/swiftui/fetchrequest)
[ObservedObject](https://link.zhihu.com/?target=https%3A//developer.apple.com/documentation/swiftui/observedobject)
[State](https://link.zhihu.com/?target=https%3A//developer.apple.com/documentation/swiftui/state)
[StateObject](https://link.zhihu.com/?target=https%3A//developer.apple.com/documentation/swiftui/stateobject)
在开发中,他们的用法可以用下边这个图概括:
如果View依赖了这些数据,当数据改变的时候,View就会刷新。我们主要讲解ObservedObject
和StateObject
。
ObservedObject
class MyViewModel: ObservableObject {
@Published var name: String = "张三"
}
struct ContentView: View {
@ObservedObject var dataModel: MyViewModel
var body: some View {
Text(dataModel.name)
}
}
上边的代码是最常见的一种用法,dataModel
为ContentView
提供数据,那么@ObservedObject
是怎么一回事呢?看它的定义:
@propertyWrapper @frozen public struct ObservedObject<ObjectType> : DynamicProperty where ObjectType : ObservableObject {
@dynamicMemberLookup @frozen public struct Wrapper {
public subscript<Subject>(dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>) -> Binding<Subject> { get }
}
public init(initialValue: ObjectType)
public init(wrappedValue: ObjectType)
public var wrappedValue: ObjectType
public var projectedValue: ObservedObject<ObjectType>.Wrapper { get }
}
通过分析上边的代码,我们发现下边几个重要信息:
ObjectType : ObservableObject
表示它的类型必须实现ObservableObject
协议,这个协议我们下边会讲到projectedValue: ObservedObject<ObjectType>.Wrapper
,说明我们可以用$dataModel
来访问这个projectedValue
,它的返回值是Wrapper
类型,再看上边struct Wrapper
的定义,它是一个@dynamicMemberLookup
,@dynamicMemberLookup
的实现原理我们后续再详细讲解,大家只需要知道,当我们想要一个Bind
类型的数据是,可以这样TextField("输入文字", text: $dataModel.name)
其中,上边的重点是ObservableObject
协议,我们再看看它的定义:
public protocol ObservableObject : AnyObject {
/// The type of publisher that emits before the object has changed.
associatedtype ObjectWillChangePublisher : Publisher = ObservableObjectPublisher where Self.ObjectWillChangePublisher.Failure == Never
/// A publisher that emits before the object has changed.
var objectWillChange: Self.ObjectWillChangePublisher { get }
}
extension ObservableObject where Self.ObjectWillChangePublisher == ObservableObjectPublisher {
/// A publisher that emits before the object has changed.
public var objectWillChange: ObservableObjectPublisher { get }
}
ObservableObject
继承自AnyObject
,这说明了实现该协议必须是class类型,而不能是struct类型。
该协议要求返回一个objectWillChange
属性,该属性必须实现Publisher
协议,上边代码中的ObservableObject
扩展已经实现了该协议,它返回的类型为ObservableObjectPublisher
,我们再看看它的定义:
final public class ObservableObjectPublisher : Publisher {
/// The kind of values published by this publisher.
public typealias Output = Void
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Never
/// Creates an observable object publisher instance.
public init()
final public func receive<S>(subscriber: S) where S : Subscriber, S.Failure == ObservableObjectPublisher.Failure, S.Input == ObservableObjectPublisher.Output
final public func send()
}
可以看出ObservableObjectPublisher
是一个很普通的Publisher
,它是一个自定义的Publisher
,对外只暴露了一个send
方法,用于通知数据发生变更,这个Publisher
并不会输出任何数据。
到目前为止,我们已经知道,只要实现了ObservableObject
协议,就能获得一个objectWillChange
,它是一个Publisher,只要调用objectWillChange.send()就可以触发View的刷新
。
我们先实现这个协议,代码如下:
class MyViewModel: ObservableObject {
@Published var name: String = "张三"
var age: Int = 20
func click() {
age = 30
objectWillChange.send()
}
}
如果我们用@Published
来包装某个属性,那么当属性的值变化时,就会自动调用objectWillChange.send()
,否则我们需要手动调用。
我们再看一下@Published
的定义:
@propertyWrapper public struct Published<Value> {
public init(wrappedValue: Value)
public init(initialValue: Value)
/// A publisher for properties marked with the `@Published` attribute.
public struct Publisher : Publisher {
/// The kind of values published by this publisher.
public typealias Output = Value
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Never
public func receive<S>(subscriber: S) where Value == S.Input, S : Subscriber, S.Failure == Published<Value>.Publisher.Failure
}
public var projectedValue: Published<Value>.Publisher { mutating get set }
}
大家只需要记住一点,它的projectedValue
是一个Publisher,要想获取到这个projectedValue
,使用$
符号,因为它是一个Publisher,所有我们就可以随意使用Combine中的内容了:
$name
.map {
"姓名是: \($0)"
}
.sink(receiveValue: {
print($0)
})
StateObject
@StateObject
和@ObservedObject
都是用来包装实现了ObservableObject
协议的属性,唯一的区别就是该属性的生命周期的管理问题。
@StateObject
的生命周期由View管理,只初始化一次,View销毁它就销毁@ObservedObject
的生命周期由我们手动管理,通常由父传给子
总结
本文并没有详细地讲解SwiftUI中的全部状态管理,只讲到了跟Combine有关系的状态,其中,最核心的是ObservableObject
协议,在真实的开发中,它绝对是最常用的技术,我们自定义的View Model中,通过组合使用一系列的pipline来操作数据,当作为Source for Truth的数据变更后,View自动进行刷新。