مرحبًا - تم طرح هذا للتو أثناء مناقشة مع صديق (ccmgrebenets)
لدي UserManager
يسترد User
كائنات من الشبكة وأرغب في عرض واجهة على UserManager
النحو التالي:
func getUsers() -> SignalProducer<User, ErrorType>
(سأعيد SignalProducer
لأنني أريد flatMap
طلب الشبكة هذا مع الطلبات إلى موارد الشبكة الأخرى).
المهم هو أنني أريد فقط start
اختتام طلب الشبكة في SignalProducer
إذا لم يكن هناك طلب قيد التنفيذ حاليًا.
ذهب حدسي لحل هذا على النحو التالي:
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!)
}
}
}
Buuuut الحالة المتغيرة في requestSignal
تشعر حقًا بـ "Un-RAC". نقدر أي مؤشرات يمكن أن تقدمها بشأن حل يكون أكثر انسجامًا مع مبادئ RAC.
هتافات! 😀
سيؤدي استخدام replayLazily
إلى تبسيط الأمور قليلاً — ثم يمكنك حفظ SignalProducer
مباشرةً بدلاً من إعادة توجيه الإشارة.
أيضًا ، هناك حالة سباق هنا ، لذلك قد ترغب في استخدام نوع RAC Atomic
. (إذا كانت هناك سلسلتان تستدعيان getUsers
في نفس الوقت ، فقد ينتهي بك الأمر بطلبين.)
أوصي أيضًا بتقسيم كود API الفعلي إلى طريقة أو فئة منفصلة. سيؤدي ذلك إلى تسهيل قراءة الكود قليلاً وفصل الاهتمامات بشكل أفضل قليلاً.
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()
}
}
}
⚠️ لقد قمت بكتابة هذا مباشرة في المتصفح ولم يتم اختباره. ⚠️
لا تزال هناك دولة ، لكنها محتواة أفضل قليلاً.
إن أفضل طريقة للقيام بذلك هي إضافة عامل تشغيل جديد يحتوي على الحالة.
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()
}
}
}
سأكون على يقين من إعادة التحقق من ذلك مقابل قاعدة رموز RAC ، وكذلك كتابة بعض الاختبارات حول عمر المنتج ، للتأكد من أنني كتبتها بشكل صحيح. ☺️
لطيف! وأعتذر عن الشفرة الشريطية من جانبي ، كان يجب أن أبدأ هذه الكتلة برسالة "هذا ليس كود منتج" 😄
سوف أتحقق من هذا عندما أكون أمام Xcode ؛ نقدر كل ما تبذلونه من البصيرة.
سأغلق هذا. لا تتردد في إعادة فتح الصفحة إذا كانت لديك أسئلة بعد الاطلاع عليها!
👍 شكرا مات
إليك طريقة أخرى للتعامل مع هذه المشكلة: استخدام 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()
}
}