Tương đương với các thuộc tính được tính bằng cách sử dụng @Published trong Swift Combine?


20

Trong Swift bắt buộc, người ta thường sử dụng các thuộc tính được tính toán để cung cấp quyền truy cập thuận tiện vào dữ liệu mà không bị trùng lặp trạng thái.

Hãy nói rằng tôi có lớp này được tạo ra để sử dụng MVC bắt buộc:

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
    }

    // ...
}

Nếu tôi muốn tạo tương đương phản ứng với Kết hợp, ví dụ để sử dụng với SwiftUI, tôi có thể dễ dàng thêm @Publishedvào các thuộc tính được lưu trữ để tạo Publishers, nhưng không phải cho các thuộc tính được tính toán.

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

Có nhiều cách giải quyết khác nhau mà tôi có thể nghĩ ra. Tôi có thể làm cho tài sản tính toán của tôi được lưu trữ thay thế và giữ cho nó được cập nhật.

Tùy chọn 1: Sử dụng trình quan sát thuộc tính:

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

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

    // ...
}

Tùy chọn 2: Sử dụng một Subscribertrong lớp của riêng tôi:

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

    // ...
}

Tuy nhiên, các cách giải quyết này không thanh lịch như các thuộc tính được tính toán. Chúng trùng lặp trạng thái và chúng không cập nhật đồng thời cả hai thuộc tính.

Điều gì sẽ tương đương với việc thêm Publishermột thuộc tính được tính vào Kết hợp?



1
Thuộc tính được tính là loại thuộc tính có tính chất dẫn xuất. Giá trị của họ phụ thuộc vào giá trị của người phụ thuộc. Vì lý do này một mình, có thể nói rằng họ không bao giờ có nghĩa là hành động như một ObservableObject. Bạn vốn đã cho rằng một ObservableObjectđối tượng sẽ có khả năng biến đổi, theo định nghĩa, không phải là trường hợp của Thuộc tính được tính toán .
nayem

bạn đã tìm thấy giải pháp chưa? Tôi đang ở trong hoàn cảnh tương tự, tôi muốn tránh nhà nước và vẫn có thể xuất bản
erotsppa

Câu trả lời:


2

Làm thế nào về việc sử dụng hạ lưu?

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

Theo cách này, đăng ký sẽ lấy phần tử từ thượng nguồn, sau đó bạn có thể sử dụng sinkhoặc assignthực hiện didSetý tưởng.


2

Tạo một nhà xuất bản mới đăng ký vào tài sản bạn muốn theo dõi.

@Published var speed: Double = 88

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

Sau đó bạn sẽ có thể quan sát nó giống như @Publishedtài sản của bạn .

private var subscriptions = Set<AnyCancellable>()


override func viewDidLoad() {
    super.viewDidLoad()

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

Không liên quan trực tiếp nhưng dù sao cũng hữu ích, bạn có thể theo dõi nhiều thuộc tính theo cách đó 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

Bạn sholud khai báo một PassthroughSubject trong ObservableObject của bạn:

class ReactiveUserManager1: ObservableObject {

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

    [...]
}

Và trong did set (will set có thể tốt hơn) của var @Published của bạn, bạn sẽ sử dụng một phương thức gọi là 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()
    }

    [...]
}

Bạn có thể kiểm tra nó trong WWDC Data Flow Talk


Bạn nên nhập Combine
Nicola Lauritano

Điều này khác với lựa chọn 1 trong câu hỏi như thế nào?
nayem

Không có bất kỳ PassthroughSubject nào trong
tùy chọn1

Chà, đó không phải là những gì tôi yêu cầu. @Publishedbao bọc và PassthroughSubjectcả hai phục vụ cùng một mục đích trong bối cảnh này. Hãy chú ý đến những gì bạn đã viết và những gì OP thực sự muốn đạt được. Có giải pháp của bạn phục vụ như là sự thay thế tốt hơn mà tùy chọn 1 thực sự?
nayem

0

quét ( : :) Chuyển đổi các phần tử từ nhà xuất bản ngược dòng bằng cách cung cấp phần tử hiện tại cho một bao đóng cùng với giá trị cuối cùng được trả về bởi bao đóng.

Bạn có thể sử dụng quét () để nhận giá trị mới nhất và hiện tại. Thí dụ:

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

}

Mã trên tương đương với điều này: (ít kết hợp)

  @Published var loading: Bool = false {
            didSet {
                if oldValue == false, loading == true {
                    NotificationCenter.default.post(name: NSNotification.Name("userStateDidChange"), object: nil)
                }
            }
        }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.