Reactivecocoa: Prinsip RAC: Cara terbaik untuk memulai `SignalProducer` hanya sekali?

Dibuat pada 22 Mei 2016  ·  5Komentar  ·  Sumber: ReactiveCocoa/ReactiveCocoa

Hai - ini baru saja muncul saat diskusi dengan teman (cc @mgrebenets)

Saya memiliki UserManager yang mengambil User objek dari jaringan dan ingin mengekspos antarmuka pada UserManager sebagai berikut:

func getUsers() -> SignalProducer<User, ErrorType>

(Saya mengembalikan SignalProducer karena saya ingin flatMap permintaan jaringan ini dengan permintaan ke sumber daya jaringan lain).

Masalahnya adalah saya hanya ingin start permintaan jaringan yang dibungkus dalam SignalProducer jika tidak ada permintaan yang sedang berlangsung.

Intuisi saya untuk menyelesaikan ini berjalan seperti ini:

class UserManager {

    static let sharedManager = UserManager()

    private var requestSignal: Signal<User, NSError>? = nil

    func getUsers() -> SignalProducer<User, NSError> {
        if let requestSignal = requestSignal {
            print("There is already a request in progress so simply return a reference to its signal")
            return SignalProducer(signal: requestSignal)
        } else {
            let requestSignalProducer = SignalProducer<User, NSError> { observer, disposable in
                let url = NSURL(string:"http://jsonplaceholder.typicode.com/users/1")!
                let task = NSURLSession.sharedSession()
                    .dataTaskWithURL(url) { (data, response, error) in
                    if error != nil {
                        observer.sendFailed(NSError(domain:"", code:5, userInfo:nil))
                    } else {
                        let json = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions())
                        let user = User(JSON: json)
                        print("Completed user request at \(NSDate())")
                        observer.sendNext(user)
                        observer.sendCompleted()
                    }
                }
                print("Started user request at \(NSDate())")
                task.resume()
            }
            requestSignalProducer.startWithSignal{ [weak self] (signal, disposable) in
                guard let `self` = self else { return }
                `self`.requestSignal = signal
                signal.observeCompleted {
                    print("Completing the user request signal")
                    `self`.requestSignal = nil
                }
            }
            return SignalProducer(signal: self.requestSignal!)
        }
    }
}

Tapi keadaan bisa berubah di requestSignal terasa benar-benar "Un-RAC". Akan sangat menghargai petunjuk yang dapat Anda berikan untuk solusi yang lebih sesuai dengan prinsip RAC.

Bersulang! 😀

question

Semua 5 komentar

Menggunakan replayLazily akan menyederhanakan banyak hal — lalu Anda dapat menyimpan SignalProducer secara langsung daripada mengarahkan sinyal.

Juga, ada kondisi balapan di sini, jadi Anda mungkin ingin menggunakan tipe RAC Atomic . (Jika 2 utas memanggil getUsers pada saat yang sama, Anda bisa mendapatkan 2 permintaan.)

Saya juga merekomendasikan untuk membagi kode API yang sebenarnya menjadi metode atau kelas terpisah. Itu akan membuat kode sedikit lebih mudah dibaca dan memisahkan masalah sedikit lebih baik.

struct API {
    static func getUsers() -> SignalProducer<User, NSError> {
            return SignalProducer<User, NSError> { observer, disposable in
                let url = NSURL(string:"http://jsonplaceholder.typicode.com/users/1")!
                let task = NSURLSession.sharedSession()
                    .dataTaskWithURL(url) { (data, response, error) in
                    if error != nil {
                        observer.sendFailed(NSError(domain:"", code:5, userInfo:nil))
                    } else {
                        let json = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions())
                        let user = User(JSON: json)
                        print("Completed user request at \(NSDate())")
                        observer.sendNext(user)
                        observer.sendCompleted()
                    }
                }
                print("Started user request at \(NSDate())")
                task.resume()
         }
    }
}

class UserManager {
    static let sharedManager = UserManager()

    private var requestProducer: Atomic<SignalProducer<User, NSError>?> = Atomic(nil)

    func getUsers() -> SignalProducer<User, NSError> {
        return requestProducer.modify { producer in
            if let producer = producer {
                print("There is already a request in progress so simply return a reference to its signal")
                return producer
            }

            return API.getUsers()
                .on(completed: {
                    self.requestProcuder.modifify { _ in nil }
                })
               .replayLazily()
        }
    }
}

⚠️ Saya mengetik ini langsung ke browser dan belum teruji. ⚠️

Masih ada negara, tetapi berisi sedikit lebih baik.

Cara RAC-iest untuk melakukannya adalah dengan menambahkan operator baru yang berisi status.

extension SignalProducer {
    func replayIfStarted() -> SignalProducer<Value, Error> {
        let active: Atomic<SignalProducer<Value, Error>?> = nil
        return active.modify { producer in
            if let producer = producer {
                return producer
            }

            return self
                .on(completed: {
                    active.modify { _ in nil }
                })
                .replayLazily()
        }
    }
}

Saya pasti akan memeriksa ulang itu terhadap basis kode RAC, dan juga menulis beberapa tes seputar masa pakai produser, untuk memastikan saya menulisnya dengan benar. ☺️

Bagus! Dan maaf untuk kode-barf di pihak saya, saya harus mengawali blok itu dengan pesan "ini bukan kode prod" 😄

Saya akan memeriksanya saat saya di depan Xcode; menghargai semua wawasan Anda.

Saya akan menutup ini. Jangan ragu untuk membuka kembali jika Anda memiliki pertanyaan setelah melihatnya!

👍 terima kasih Matt

Berikut cara lain untuk mengatasi masalah ini: menggunakan Property !

final class UserManager {
    /// The public API is as easy as observing this.
    public let users: AnyProperty<[User]>
    private let usersMutableProperty = MutableProperty<[User]>([])

    init() {
        self.users = AnyProperty(self.usersMutableProperty)  
    }

    private func getUsers() -> SignalProducer<User, NSError> { }

    public func requestUsers() {
        /// This is not idea because multiple calls to this method would step onto one-another, just doing it like this for simplicity
        self.usersMutableProperty <~ self.usersRequest()
    }
}
Apakah halaman ini membantu?
0 / 5 - 0 peringkat