Swift Combine'da @Published kullanarak hesaplanan özelliklere eşdeğer mi?


20

Zorunlu Swift'te, durumu çoğaltmadan verilere kolay erişim sağlamak için hesaplanan özelliklerin kullanılması yaygındır.

Diyelim ki bu sınıf zorunlu MVC kullanımı için yapılmış:

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
    }

    // ...
}

Kombine ile reaktif bir eşdeğer oluşturmak istiyorsanız, örneğin SwiftUI ile kullanmak @Publishediçin, Publishers oluşturmak için saklanan özelliklere kolayca ekleyebilirim , ancak hesaplanan özellikler için değil.

    @Published var userIsLoggedIn: Bool { // Error: Property wrapper cannot be applied to a computed property
        currentUser != nil
    }

Aklıma gelen çeşitli çözümler var. Bunun yerine hesaplanan mülkümü saklayabilir ve güncel tutabilirim.

Seçenek 1: Özellik gözlemcisi kullanma:

class ReactiveUserManager1: ObservableObject {
    @Published private(set) var currentUser: User? {
        didSet {
            userIsLoggedIn = currentUser != nil
        }
    }

    @Published private(set) var userIsLoggedIn: Bool = false

    // ...
}

Seçenek 2: SubscriberKendi sınıfımda a kullanma :

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

    // ...
}

Ancak, bu geçici çözümler hesaplanan özellikler kadar zarif değildir. Durumu çoğaltırlar ve her iki özelliği de aynı anda güncellemezler.

PublisherCombine'da hesaplanan bir özelliğe a eklemek için uygun eşdeğer ne olur ?



1
Hesaplanan Özellikler türetilen özelliklerdir. Onların değerleri bağımlıların değerlerine bağlıdır. Bu sebepten ötürü, asla bir gibi davranmayacakları söylenebilir ObservableObject. Doğası gereği, bir ObservableObjectnesnenin, Muhasebeleştirilmiş Özellik için geçerli olmayan bir mutasyon yeteneğine sahip olması gerektiğini varsayarsınız .
nayem

Buna bir çözüm buldunuz mu? Aynı durumdayım, devletten kaçınmak ve hala yayınlamak istiyorum
erotsppa

Yanıtlar:


2

Aşağı akım kullanmaya ne dersiniz?

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

Bu şekilde, abonelik yukarı akıştan eleman alacak, sonra bu fikri sinkveya assignyapmak için kullanabilirsiniz didSet.


2

İzlemek istediğiniz mülke abone olan yeni bir yayıncı oluşturun.

@Published var speed: Double = 88

lazy var canTimeTravel: AnyPublisher<Bool,Never> = {
    $speed
        .map({ $0 >= 88 })
        .eraseToAnyPublisher()
}()

Daha sonra @Publishedmülkünüz gibi gözlemleyebilirsiniz .

private var subscriptions = Set<AnyCancellable>()


override func viewDidLoad() {
    super.viewDidLoad()

    sourceOfTruthObject.$canTimeTravel.sink { [weak self] (canTimeTravel) in
        // Do something…
    })
    .store(in: &subscriptions)
}

Doğrudan ilgili değil, ancak yine de yararlı, birden çok özelliği bu şekilde izleyebilirsiniz 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 öğenizde bir PassthroughSubject bildirmelisiniz :

class ReactiveUserManager1: ObservableObject {

    //The PassthroughSubject provides a convenient way to adapt existing imperative code to the Combine model.
    var objectWillChange = PassthroughSubject<Void,Never>()

    [...]
}

Ve didSet da arasında (willSet daha iyi olabilir) @Published var bir yöntemi denir kullanacağız ) (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 Veri Akışı Konuşması'nda kontrol edebilirsiniz


Kombine ithal etmelisiniz
Nicola Lauritano

Bu sorunun kendisinde 1. seçenekten ne farkı var ?
nayem

Seçenek1'de herhangi bir PassthroughSubject yok
Nicola Lauritano

Aslında sorduğum şey bu değildi. @Publishedsargı ve PassthroughSubjecther ikisi de bu bağlamda aynı amaca hizmet etmektedir. Yazdıklarınıza ve OP'nin gerçekte neyi başarmak istediğine dikkat edin. Çözümünüz aslında 1. seçenekten daha iyi bir alternatif olarak mı hizmet veriyor ?
nayem

0

scan ( : :) Geçerli öğeyi, kapağın döndürdüğü son değerle birlikte bir kapağa sağlayarak öğeleri yukarı akış yayıncısından dönüştürür.

En son ve geçerli değeri almak için scan () işlevini kullanabilirsiniz. Misal:

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

}

Yukarıdaki kod buna eşdeğerdir: (daha az Kombine)

  @Published var loading: Bool = false {
            didSet {
                if oldValue == false, loading == true {
                    NotificationCenter.default.post(name: NSNotification.Name("userStateDidChange"), object: nil)
                }
            }
        }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.