Swift Combine에서 @Published를 사용하여 계산 된 속성과 동등합니까?


20

명령형 스위프트에서는 계산 된 속성을 사용하여 상태를 복제하지 않고도 데이터에 편리하게 액세스 할 수 있습니다.

이 클래스가 명령형 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)
    }

    // ...
}

그러나 이러한 해결 방법은 계산 된 속성만큼 우아하지 않습니다. 상태를 복제하고 두 속성을 동시에 업데이트하지 않습니다.

PublisherCombine에서 계산 된 속성에 a 를 추가하는 것과 동등한 적절한 것은 무엇입니까 ?



1
계산 된 속성은 파생 된 속성의 종류입니다. 그들의 가치는 부양 가족의 가치에 달려 있습니다. 이러한 이유만으로, 그들은 결코 같은 행동을하도록 의도되지 않았다고 말할 수 있습니다 ObservableObject. 본질적으로 ObservableObject객체가 돌연변이 속성 을 가질 수 있어야 한다고 가정합니다. 이 기능은 정의상 Computed Property 의 경우와 다릅니다 .
nayem

이것에 대한 해결책을 찾았습니까? 저는 똑같은 상황에 처해 있습니다. 저는 주를 피하고 여전히 출판 할 수 있기를
원합니다.

답변:


2

다운 스트림을 사용하는 것은 어떻습니까?

lazy var userIsLoggedInPublisher: AnyPublisher = $currentUser
                                          .map{$0 != nil}
                                          .eraseToAnyPublisher()

이런 식으로 구독은 업스트림에서 요소를 가져온 다음 아이디어 를 사용 sink하거나 assign수행 할 수 있습니다 didSet.


2

추적하려는 속성에 가입 한 새 게시자를 만듭니다.

@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()
}()

0

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 에서 확인할 수 있습니다


Combine
Import to Nicola

질문 자체 의 옵션 1 과 다른 점은 무엇입니까?
nayem

이 옵션에는 PassthroughSubject가 없습니다 1
Nicola Lauritano

글쎄, 그건 내가 실제로 요구 한 것이 아니 었습니다. @Published랩퍼와 PassthroughSubject둘 다이 컨텍스트에서 동일한 목적을 제공합니다. 작성한 내용과 OP가 실제로 원하는 것을주의하십시오. 귀하의 솔루션이 옵션 1 보다 더 나은 대안으로 작용합니까 ?
nayem

0

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)
                }
            }
        }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.