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! 😀
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()
}
}