명령형 스위프트에서는 계산 된 속성을 사용하여 상태를 복제하지 않고도 데이터에 편리하게 액세스 할 수 있습니다.
이 클래스가 명령형 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
}
// ...
}
예를 들어 SwiftUI와 함께 사용하기 위해 Combine을 사용하여 반응 형 등가물을 생성하려는 경우 @Published
저장된 속성에 쉽게 추가하여을 생성 할 수 Publisher
있지만 계산 된 속성에는 사용할 수 없습니다.
@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
내 수업에서 a 사용 :
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)
}
// ...
}
그러나 이러한 해결 방법은 계산 된 속성만큼 우아하지 않습니다. 상태를 복제하고 두 속성을 동시에 업데이트하지 않습니다.
Publisher
Combine에서 계산 된 속성에 a 를 추가하는 것과 동등한 적절한 것은 무엇입니까 ?
답변
다운 스트림을 사용하는 것은 어떻습니까?
lazy var userIsLoggedInPublisher: AnyPublisher = $currentUser
.map{$0 != nil}
.eraseToAnyPublisher()
이런 식으로 구독은 업스트림에서 요소를 가져온 다음 아이디어 를 사용 sink
하거나 assign
수행 할 수 있습니다 didSet
.
답변
추적하려는 속성에 가입 한 새 게시자를 만듭니다.
@Published var speed: Double = 88
lazy var canTimeTravel: AnyPublisher<Bool,Never> = {
$speed
.map({ $0 >= 88 })
.eraseToAnyPublisher()
}()
그러면 @Published
재산 과 매우 흡사하게 관찰 할 수 있습니다.
private var subscriptions = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
sourceOfTruthObject.$canTimeTravel.sink { [weak self] (canTimeTravel) in
// Do something…
})
.store(in: &subscriptions)
}
그럼에도 불구하고 직접 관련이 없지만 유용하지만을 사용 하여 여러 속성을 추적 할 수 있습니다 combineLatest
.
@Published var threshold: Int = 60
@Published var heartData = [Int]()
/** This publisher "observes" both `threshold` and `heartData`
and derives a value from them.
It should be updated whenever one of those values changes. */
lazy var status: AnyPublisher<Status,Never> = {
$threshold
.combineLatest($heartData)
.map({ threshold, heartData in
// Computing a "status" with the two values
Status.status(heartData: heartData, threshold: threshold)
})
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}()
답변
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)
}
}
}