命令型Swiftでは、状態を複製することなくデータへの便利なアクセスを提供するために計算されたプロパティを使用するのが一般的です。
命令型MVCを使用するために作成されたこのクラスがあるとします。
class ImperativeUserManager {
private(set) var currentUser: User? {
didSet {
if oldValue != currentUser {
NotificationCenter.default.post(name: NSNotification.Name("userStateDidChange"), object: nil)
// Observers that receive this notification might then check either currentUser or userIsLoggedIn for the latest state
}
}
}
var userIsLoggedIn: Bool {
currentUser != nil
}
// ...
}
Combineを使用してリアクティブな同等物を作成する場合は、たとえば、 SwiftUIで使用するために、簡単に@Published
を格納されたプロパティに変換してPublisher
sを生成しますが、計算されたプロパティは対象外です。
@Published var userIsLoggedIn: Bool { // Error: Property wrapper cannot be applied to a computed property
currentUser != nil
}
考えられるさまざまな回避策があります。代わりに、計算されたプロパティを保存して、更新し続けることができます。
オプション1:プロパティオブザーバーの使用:
class ReactiveUserManager1: ObservableObject {
@Published private(set) var currentUser: User? {
didSet {
userIsLoggedIn = currentUser != nil
}
}
@Published private(set) var userIsLoggedIn: Bool = false
// ...
}
オプション2:自分のクラスでSubscriber
を使用する:
class ReactiveUserManager2: ObservableObject {
@Published private(set) var currentUser: User?
@Published private(set) var userIsLoggedIn: Bool = false
private var subscribers = Set<AnyCancellable>()
init() {
$currentUser
.map { $0 != nil }
.assign(to: \.userIsLoggedIn, on: self)
.store(in: &subscribers)
}
// ...
}
ただし、これらの回避策は、計算されたプロパティほど洗練されていません。それらは状態を複製し、両方のプロパティを同時に更新しません。
Combineで計算されたプロパティにPublisher
を追加することと同等の適切なものは何ですか?
ダウンストリームを使用するのはどうですか?
lazy var userIsLoggedInPublisher: AnyPublisher = $currentUser
.map{$0 != nil}
.eraseToAnyPublisher()
このようにして、サブスクリプションは上流から要素を取得し、sink
またはassign
を使用してdidSet
アイデアを実行できます。
ObservableObjectでPassthroughSubjectを宣言する必要があります。
class ReactiveUserManager1: ObservableObject {
//The PassthroughSubject provides a convenient way to adapt existing imperative code to the Combine model.
var objectWillChange = PassthroughSubject<Void,Never>()
[...]
}
そして、あなたの@ Published varのdidSet(willSetの方が良いかもしれません)では、send( )
class ReactiveUserManager1: ObservableObject {
//The PassthroughSubject provides a convenient way to adapt existing imperative code to the Combine model.
var objectWillChange = PassthroughSubject<Void,Never>()
@Published private(set) var currentUser: User? {
willSet {
userIsLoggedIn = currentUser != nil
objectWillChange.send()
}
[...]
}
WWDC Data Flow Talk で確認できます。
scan(: :)上流パブリッシャーからの要素を変換します。現在の要素をクロージャに提供し、クロージャから返された最後の値を提供します。
Scan()を使用して、最新の現在の値を取得できます。例:
@Published var loading: Bool = false
init() {
// subscriber connection
$loading
.scan(false) { latest, current in
if latest == false, current == true {
NotificationCenter.default.post(name: NSNotification.Name("userStateDidChange"), object: nil)
}
return current
}
.sink(receiveValue: { _ in })
.store(in: &subscriptions)
}
上記のコードはこれと同等です:(結合が少ない)
@Published var loading: Bool = false {
didSet {
if oldValue == false, loading == true {
NotificationCenter.default.post(name: NSNotification.Name("userStateDidChange"), object: nil)
}
}
}