เทียบเท่ากับคุณสมบัติที่คำนวณได้โดยใช้ @Published ใน Swift Combine?


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 ฉันสามารถเพิ่ม@Publishedคุณสมบัติที่เก็บไว้เพื่อสร้างPublishers แต่ไม่ใช่สำหรับคุณสมบัติที่คำนวณได้

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

    // ...
}

อย่างไรก็ตามวิธีแก้ไขปัญหาเหล่านี้ไม่หรูหราเท่าคุณสมบัติการคำนวณ พวกเขาทำซ้ำสถานะและพวกเขาไม่ปรับปรุงคุณสมบัติทั้งสองพร้อมกัน

สิ่งที่จะเทียบเท่าที่เหมาะสมในการเพิ่มPublisherคุณสมบัติในการคำนวณในการรวม?



1
คุณสมบัติที่คำนวณได้เป็นประเภทของคุณสมบัติที่ได้รับคุณสมบัติ ค่าของพวกเขาขึ้นอยู่กับค่าของการพึ่งพา ObservableObjectด้วยเหตุนี้คนเดียวก็อาจกล่าวได้ว่าพวกเขาจะไม่ได้ตั้งใจที่จะทำหน้าที่เหมือน คุณคิดโดยเนื้อแท้ว่าObservableObjectวัตถุควรมีความสามารถในการกลายพันธุ์ซึ่งตามนิยามแล้วจะไม่เกิดขึ้นกับคุณสมบัติที่คำนวณได้
nayem

คุณพบวิธีแก้ปัญหานี้หรือไม่? ฉันอยู่ในสถานการณ์เดียวกันฉันต้องการหลีกเลี่ยงสถานะและยังสามารถเผยแพร่ได้
erotsppa

คำตอบ:


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

คุณ sholud ประกาศPassthroughSubjectใน ObservableObject ของคุณ:

class ReactiveUserManager1: ObservableObject {

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

    [...]
}

และใน didSet (willSet น่าจะดีกว่า) ของ@Published var ของคุณคุณจะใช้วิธีที่เรียกว่า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


คุณควรนำเข้ารวม
Nicola Lauritano

สิ่งนี้แตกต่างจากตัวเลือก 1ในคำถามอย่างไร
nayem

ไม่มี PassthroughSubject ในตัวเลือก 1
Nicola Lauritano

นั่นไม่ใช่สิ่งที่ฉันถามจริง @Publishedเสื้อคลุมและPassthroughSubjectทั้งสองตอบสนองวัตถุประสงค์เดียวกันในบริบทนี้ ให้ความสนใจกับสิ่งที่คุณเขียนและสิ่งที่ผู้ปฏิบัติการต้องการให้บรรลุ โซลูชันของคุณทำหน้าที่เป็นทางเลือกที่ดีกว่าที่ตัวเลือก 1จริงหรือไม่
nayem

0

สแกน ( : :) แปลงองค์ประกอบจากผู้เผยแพร่ต้นน้ำโดยจัดทำองค์ประกอบปัจจุบันเป็นการปิดพร้อมกับค่าสุดท้ายที่ส่งคืนโดยการปิด

คุณสามารถใช้ 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.