Reactivecocoa: مبادئ RAC: ما هي أفضل طريقة لبدء "SignalProducer" مرة واحدة فقط؟

تم إنشاؤها على ٢٢ مايو ٢٠١٦  ·  5تعليقات  ·  مصدر: ReactiveCocoa/ReactiveCocoa

مرحبًا - تم طرح هذا للتو أثناء مناقشة مع صديق (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.

هتافات! 😀

question

ال 5 كومينتر

سيؤدي استخدام 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()
    }
}
هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات