جزء RxSwift 1

ReactiveX logo


يوم جيد ، هابروفسك. في هذه السلسلة من المقالات ، أود أن أتحدث عن البرمجة التفاعلية ، وهي الإطار
RxSwift . كانت هناك مقالات على RxSwift عن حبري والشبكة ، لكنها في رأيي صعبة للغاية بالنسبة للمبتدئين. لذلك ، إذا بدأت في فهم البرمجة التفاعلية في iOS ، فأنا أطلب القط.


لنبدأ بتحديد ماهية البرمجة التفاعلية.


البرمجة التفاعلية هي نموذج برمجي يركز على تدفقات البيانات وانتشار التغيير.

هذا ما تخبرنا به ويكيبيديا العظيمة.


بمعنى آخر ، في حالة البرمجة بأسلوب حتمي ، نكتب في الكود مجموعة من الأوامر التي يجب تنفيذها بالتتابع. يلتزم نمط البرمجة التفاعلية بالعديد من المفاهيم الأخرى. مع أسلوب البرمجة التفاعلية ، فإن برنامجنا هو "مستمع" لتغيرات الحالة في الكائنات التي نلاحظها. يبدو الأمر معقدًا ، لكنه ليس كذلك ، يكفي فقط اختراق هذا المفهوم وسيصبح كل شيء سهلًا وواضحا للغاية ، لا البق حتى الان .


لن أرسم كيفية تثبيت الإطار ، فمن السهل القيام بذلك عن طريق النقر على الرابط . هيا نتدرب.


ملحوظة


لنبدأ بملاحظة بسيطة أو مهمة يمكن ملاحظتها أو ملاحظتها. ملحوظة هي ما سيعطينا البيانات ، هناك حاجة لإنشاء دفق البيانات.


let observable = Observable<String>.just(" observable") 

البنغو ! أنشأنا أول ملحوظة.


اذا ماذا


نظرًا لأننا أنشأنا الكائن المرصود ، فمن المنطقي أننا نحتاج إلى إنشاء كائن سيلاحظ.


 let observable = Observable<String>.just(" observable") _ = observable.subscribe { (event) in print(event) } 

نحصل على ما يلي في السجل:


 next( observable) completed 

أكملت؟


يرصدنا ملحوظا معلومات عن أحداثه ، هناك 3 أنواع فقط:


  • التالي
  • خطأ
  • اكتمل

إلى جانب العنصر التالي يأتي العنصر الذي أرسلناه وجميع الأحداث التي أرسلناها ، يتم إرسال الخطأ كما يوحي الاسم في حالة حدوث خطأ ، ويتم إكماله في الحالة التي يرسل فيها ملاحظتنا جميع البيانات وينتهي.


يمكننا إنشاء المزيد من التفاصيل مراقب المشتركين والحصول على عرض أكثر ملاءمة للتعامل مع جميع الأحداث.


 _ = observable.subscribe(onNext: { (event) in print(event) }, onError: { (error) in print(error) }, onCompleted: { print("finish") }) { print("disposed") // ,          } 

   finish disposed 

في الملاحظة ، يمكنك إنشاء تسلسل ليس فقط من سطر واحد ، وفي الواقع ليس فقط من الخطوط ، يمكننا وضع أي نوع من البيانات هناك.


 let sequence = Observable<Int>.of(1, 2, 4, 5, 6) _ = sequence.subscribe { (event) in print(event) } 

 next(1) next(2) ... completed 

يمكن إنشاء ملحوظة من مجموعة من القيم.


 let array = [1, 2, 3] let observable = Observable<Int>.from(array) _ = observable.subscribe { (event) in print(event) } 

 next(1) next(2) next(3) completed 

يمكن للمرء أن يلاحظ وجود أي عدد من المشتركين . الآن المصطلحات ، ما هو ملحوظ؟


يمكن ملاحظته هو أساس كل Rx ، والذي يولد بشكل غير متزامن سلسلة من البيانات الثابتة ويسمح للآخرين بالاشتراك فيها.


التخلص


الآن بعد أن عرفنا كيفية إنشاء تسلسل والاشتراك فيها ، نحتاج إلى التعامل مع شيء مثل التخلص .


من المهم أن نتذكر أن Observable هو نوع "بارد" ، أي أن ملاحظتنا لا "تنبعث" أي أحداث حتى يتم الاشتراك فيها. يمكن ملاحظته حتى يرسل رسالة خطأ أو رسالة خطأ ( مكتمل ). إذا أردنا إلغاء الاشتراك بشكل صريح ، فيمكننا القيام بما يلي.


 // №1 //   let array = [1, 2, 3] // observable    let observable = Observable<Int>.from(array) //   observable let subscription = observable.subscribe { (event) in print(event) } //dispos'    subscription.dispose() 

هناك المزيد جميل الخيار الصحيح.


 //  "" let bag = DisposeBag() //   let array = [1, 2, 3] // observable    let observable = Observable<Int>.from(array) //   observable _ = observable.subscribe { (event) in print(event) }.disposed(by: bag) 

وبالتالي ، نضيف اشتراكنا إلى حقيبة إعادة التدوير أو إلى DisposeBag .
ما هذا؟ إذا كنت ، باستخدام اشتراك ، لا تضيفه إلى حقيبتك أو لا تستدعي صراحة التخلص ، أو ، في الحالات القصوى ، لا تجعل الملاحظة قابلة للإكمال بأي شكل من الأشكال ، فعندئذٍ على الأرجح ستحصل على تسرب للذاكرة. ستستخدم DisposeBag كثيرًا في عملك مع RxSwift.


عوامل التشغيل


تحتوي البرمجة الوظيفية التفاعلية (FRP أدناه) على العديد من عوامل التشغيل المضمنة لتحويل العناصر القابلة للملاحظة. هناك موقع rxmarbles ، يمكنك أن ترى فيه عمل وتأثير جميع المشغلين ، ولكننا ما زلنا ننظر إلى بعض منهم.


الخريطة


يتم استخدام عامل تشغيل الخرائط في كثير من الأحيان وأعتقد أنه مألوف للكثيرين ، فبمساعدته نقوم بتحويل جميع العناصر المستلمة.
مثال:


 let bag = DisposeBag() let array = [1, 2, 3] let observable = Observable<Int>.from(array).map { $0 * 2 } _ = observable.subscribe { (e) in print(e) }.disponsed(by: bag) 

ماذا نحصل في وحدة التحكم:


 next(2) next(4) next(6) completed 

نأخذ كل عنصر من عناصر التسلسل وننشئ تسلسلًا جديدًا ناتجًا. لتوضيح ما يحدث ، من الأفضل كتابة المزيد.


 let bag = DisposeBag() let observable = Observable<Int>.from(array) //  observable let transformObservable = observable.map { $0 * 2 } _ = transformObservable.subscribe { (e) in print(e) }.disposed(by: bag) 

ما هو $ 0؟


$ 0 هو اسم العنصر افتراضيًا ، يمكننا استخدام السجلات المختصرة والكامل بالطرق ، وغالبًا ما نستخدم السجلات المختصرة.


 //  let transformObservable = observable.map { $0 * 2 } //  let transformObservable = observable.map { (element) -> Int in return element * 2 } 

توافق على أن النموذج المختصر أبسط بكثير ، أليس كذلك؟


مرشح


يسمح لنا عامل التصفية بتصفية البيانات المنبعثة من ملاحظتنا ، أي عند الاشتراك ، لن نتلقى قيمًا غير ضرورية بالنسبة لنا.
مثال:


 let bag = DisposeBag() let array = [1, 2, 3, 4, 5 , 6, 7] // observable   let observable = Observable<Int>.from(array) //  filter,     observable let filteredObservable = observable.filter { $0 > 2 } // _ = filteredObservable.subscribe { (e) in print(e) }.disposed(by: bag) 

ماذا نحصل في وحدة التحكم؟


 next(3) next(4) next(5) ... completed 

كما نرى ، في وحدة التحكم لدينا فقط تلك القيم التي تلبي شروطنا.


بالمناسبة ، يمكن الجمع بين عوامل التشغيل ، هكذا ستبدو إذا أردنا تطبيق عامل التصفية وعامل الخريطة على الفور.


 let bag = DisposeBag() let array = [1, 2, 3, 4, 5 , 6, 7] let observable = Observable<Int>.from(array) let filteredAndMappedObservable = observable .filter { $0 > 2 } .map { $0 * 2 } _ = filteredAndMappedObservable.subscribe { (e) in print(e) }.disposed(by: bag) 

وحدة التحكم:


 next(6) next(8) next(10) next(12) next(14) completed 

مميز


عامل ممتاز آخر مرتبط بالفلترة ، يتيح لك المشغل المميز تخطي البيانات المتغيرة فقط ، فمن الأفضل أن تتحول على الفور إلى مثال وسيصبح كل شيء واضحًا.


 let bag = DisposeBag() let array = [1, 1, 1, 2, 3, 3, 5, 5, 6] let observable = Observable<Int>.from(array) let filteredObservable = observable.distinctUntilChanged() _ = filteredObservable.subscribe { (e) in print(e) }.disposed(by: bag) 

في وحدة التحكم ، نحصل على ما يلي:


 next(1) next(2) next(3) next(5) next(6) completed 

بمعنى ، إذا كان العنصر الحالي للتسلسل مطابقًا للعنصر السابق ، فإنه يتم تخطيه وهكذا حتى يظهر عنصر مختلف عن العنصر السابق ، فهو مناسب جدًا عند العمل مع واجهة المستخدم ، على سبيل المثال ، الجدول ، في حالة تلقينا البيانات هي نفسها التي لدينا الآن ، ثم لا ينبغي إعادة تحميل الجدول.


Takelast


مشغل TakeLast بسيط للغاية ، نأخذ العدد التاسع من العناصر من النهاية.


 let bag = DisposeBag() let array = [1, 1, 1, 2, 3, 3, 5, 5, 6] let observable = Observable<Int>.from(array) let takeLastObservable = observable.takeLast(1) _ = takeLastObservable.subscribe { (e) in print(e) }.disposed(by: bag) 

في وحدة التحكم نحصل على ما يلي:


 next(6) completed 

خنق وفاصل زمني


ثم قررت أخذ عاملين في وقت واحد ، ببساطة لأنه بمساعدة الثاني ، من السهل إظهار عمل الأول.


يتيح لك عامل التحكم في الخانق أخذ استراحة بين التقاط القيم المرسلة ، إنه مثال بسيط جدًا ، حيث تكتب تطبيقًا تفاعليًا ، وتستخدم شريط البحث ولا تريد إما إعادة تحميل الجدول أو الانتقال إلى الخادم في كل مرة تقوم فيها بإدخال البيانات ، لذلك تستخدم الخانق وبالتالي تقول هل ترغب في أخذ بيانات المستخدم كل ثانيتين (على سبيل المثال ، يمكنك تعيين أي فاصل زمني) وتقليل استهلاك الموارد للمعالجة غير الضرورية ، وكيف يعمل ويتم وصفه في التعليمات البرمجية؟ انظر أدناه للحصول على مثال.


 let bag = DisposeBag() //observable     0.5    1   0 let observable = Observable<Int>.interval(0.5, scheduler: MainScheduler.instance) let throttleObservable = observable.throttle(1.0, scheduler: MainScheduler.instance) _ = takeLastObservable.subscribe { (event) in print("throttle \(event)") }.disposed(by: bag) 

في وحدة التحكم سيكون:


 throttle next(0) throttle next(2) throttle next(4) throttle next(6) ... 

يؤدي عامل الفاصل الزمني إلى إنشاء قيم يمكن ملاحظتها كل 0.5 ثانية بزيادات قدرها 1 بدءًا من 0 ، وهو جهاز توقيت بسيط لـ Rx. اتضح أنه بمجرد إنشاء القيم كل 0.5 ثانية ، يتم إنشاء قيمتين في الثانية ، وحساب بسيط ، وينتظر عامل الخانق ثانية ويأخذ القيمة الأخيرة.


تنحي


يشبه Debounce إلى حد كبير البيان السابق ، ولكنه أكثر ذكاءً قليلاً في رأيي. ينتظر عامل التنحية الفترة الزمنية التاسعة ، وإذا لم تكن هناك تغييرات منذ بداية المؤقت ، فإنه يأخذ القيمة الأخيرة ، إذا أرسلنا القيمة ، فسيتم إعادة تشغيل المؤقت مرة أخرى. هذا مفيد جدًا للحالة الموضحة في المثال السابق ، يقوم المستخدم بإدخال البيانات ، ننتظر عند الانتهاء (إذا كان المستخدم غير نشط لمدة ثانية أو نصف) ، ثم نبدأ في تنفيذ بعض الإجراءات. لذلك ، إذا قمنا ببساطة بتغيير عامل التشغيل في الكود السابق ، فلن نحصل على القيم إلى وحدة التحكم ، لأن الانتظار سوف ينتظر ثانية ، ولكن كل 0.5 ثانية سيحصل على قيمة جديدة ويعيد تشغيل موقته ، لذلك لن نحصل على أي شيء. دعنا نرى مثالاً.


 let bag = DisposeBag() let observable = Observable<Int>.interval(1.5, scheduler: MainScheduler.instance) let debounceObservable = observable.debounce(1.0, scheduler: MainScheduler.instance) _ = debounceObservable.subscribe({ (e) in print("debounce \(e)") }).disposed(by: bag) 

في هذه المرحلة ، أقترح الانتهاء مع المشغلين ، هناك الكثير منهم في إطار RxSwift ، ولا يمكن القول بأنهم جميعًا ضروريون جدًا في الحياة اليومية ، ولكن لا تزال بحاجة إلى معرفة وجودهم ، لذلك من المستحسن التعرف على القائمة الكاملة للمشغلين على موقع rxmarbles .


المجدول


الموضوع المهم الذي أود أن أتطرق إليه في هذه المقالة هو المجدول. جدولة ، تسمح لنا بتشغيل ملاحظاتنا على بعض المواضيع ولديهم التفاصيل الدقيقة الخاصة بهم. للبدء ، هناك نوعان من إعداد جدولة يمكن ملاحظتها - [observeOn] () و [subscribeOn] ().


اكتتاب


SubscribeOn مسؤول عن سلسلة العمليات التي ستعمل فيها العملية التي يمكن ملاحظتها بالكامل حتى تصل أحداثها إلى المعالج (المشترك).


Observeon


كما قد تخمن ، فإن شركة ObserverveOn مسؤولة عن تدفق الأحداث التي يستقبلها المشترك.


هذا أمر رائع جدًا ، لأنه يمكننا بسهولة وضع تنزيل شيء ما من الشبكة في دفق الخلفية ، وعندما نحصل على النتيجة ، انتقل إلى الدفق الرئيسي والتصرف بطريقة ما على واجهة المستخدم.


دعونا نرى كيف يعمل هذا مع مثال:


 let observable = Observable<Int>.create { (observer) -> Disposable in print("thread observable -> \(Thread.current)") observer.onNext(1) observer.onNext(2) return Disposables.create() }.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) _ = observable .observeOn(MainScheduler.instance) .subscribe({ (e) in print("thread -> \(Thread.current)") print(e) }) 

في وحدة التحكم نحصل على:


 thread observable -> <NSThread: 0x604000465040>{number = 3, name = (null)} thread -> <NSThread: 0x60000006f6c0>{number = 1, name = main} next(1) thread -> <NSThread: 0x60000006f6c0>{number = 1, name = main} next(2) 

نرى أنه تم إنشاء ملحوظة في سلسلة المحادثات الخلفية ، وعالجنا البيانات في سلسلة المحادثات الرئيسية. هذا مفيد عند العمل مع شبكة ، على سبيل المثال:


 let rxRequest = URLSession.shared.rx.data(request: URLRequest(url: URL(string: "http://jsonplaceholder.typicode.com/posts/1")!)).subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) _ = rxRequest .observeOn(MainScheduler.instance) .subscribe { (event) in print(" \(event)") print("thread \(Thread.current)") } 

وبالتالي ، سيتم تنفيذ الطلب في مؤشر ترابط الخلفية ، وستحدث جميع معالجة الاستجابة بشكل رئيسي. في هذه المرحلة ، من السابق لأوانه تحديد نوع طريقة rx URLSession التي رسمها فجأة ، وسيتم مناقشتها لاحقًا ، وقد تم تقديم هذا الرمز كمثال على استخدام المجدول ، بالمناسبة ، سنحصل على الاستجابة التالية إلى وحدة التحكم.


 curl -X GET "http://jsonplaceholder.typicode.com/posts/1" -i -v Success (305ms): Status 200 ** next(292 bytes)** thread -> <NSThread: 0x600000072580>{number = 1, name = main}  completed thread -> <NSThread: 0x600000072580>{number = 1, name = main} 

في النهاية ، دعنا نرى نوع البيانات التي أتت إلينا ، لذلك سيتعين علينا إجراء فحص حتى لا نبدأ تحليل الرسالة المكتملة عن طريق الصدفة.


 _ = rxRequest .observeOn(MainScheduler.instance) .subscribe { (event) in if (!event.isCompleted && event.error == nil) { let json = try? JSONSerialization.jsonObject(with: event.element!, options: []) print(json!) } print("data -> \(event)") print("thread -> \(Thread.current)") } 

نتحقق من أن الحدث ليس رسالة إيقاف ملحوظة أو خطأ جاء منه ، على الرغم من أنه كان من الممكن تنفيذ طريقة اشتراك مختلفة ومعالجة جميع هذه الأنواع من الأحداث بشكل منفصل ، ولكن يمكنك فعل ذلك بنفسك بالفعل ، وسوف نحصل على ما يلي في وحدة التحكم.


 curl -X GET "http://jsonplaceholder.typicode.com/posts/1" -i -v Success (182ms): Status 200 { body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"; id = 1; title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"; userId = 1; } data -> next(292 bytes) thread -> <NSThread: 0x60400006c6c0>{number = 1, name = main} data -> completed thread -> <NSThread: 0x60400006c6c0>{number = 1, name = main} 

البيانات الواردة :-)


الموضوعات


ننتقل إلى السخونة ، وبالتحديد من "البارد" أو "السلبي" الذي يمكن ملاحظته إلى "الحار" أو "النشط" الذي يمكن ملاحظته ، والتي تسمى شخص خاضع. إذا بدأنا قبل أن نلاحظ عملنا فقط بعد الاشتراك فيها وكان لديك سؤال في رأسك ، "لماذا أحتاج إلى كل هذا؟" ، فإن الموضوعات تعمل دائمًا وترسل البيانات المستلمة دائمًا.


كيف ذلك؟ في حالة الملاحظة ، ذهبنا إلى العيادة ، ذهبنا إلى الجدة الشريرة استقبال إلى الاستقبال ، اقتربوا وسألوا عن المكتب الذي يجب أن نذهب إليه ، ثم رد علينا التحبيب ، في حالة الموضوعات ، يقف الحبيبات ويستمع إلى جدول وحالة الأطباء في المستشفى وبمجرد تلقيه معلومات حول حركة أي طبيب ، قال على الفور هذا ، اسأل شيء ما عديم الجدوى في التحبيب ، يمكننا فقط أن نصعد ، ونستمع إليها ، ونغادر ، وستستمر في القول ، أن شيئًا ما يتم نقله من خلال المقارنات ، فلننتقل إلى الكود.


دعنا ننشئ موضوعًا واحدًا واثنين من المشتركين ، وننشئ الأول مباشرة بعد الموضوع ، ونرسل القيمة إلى الموضوع ، ثم ننشئ الثاني ونرسل قيمتين إضافيتين.


 let subject = PublishSubject<Int>() subject.subscribe { (event) in print("  \(event)") } subject.onNext(1) _ = subject.subscribe { (event) in print("  \(event)") } subject.onNext(2) subject.onNext(3) subject.onNext(4) 

ماذا سنرى في وحدة التحكم؟ حسنًا ، تمكن الأول من الحصول على الحدث الأول ، لكن الثاني لم ينجح.


   next(1)   next(2)   next(2)   next(3)   next(3)   next(4)   next(4) 

هل أنت أكثر ملاءمة لفكرتك حول البرمجة التفاعلية؟
تأتي الموضوعات في عدة أشكال ؛ كلهم ​​يختلفون في كيفية إرسال القيم.


PublishSubject هو الأبسط ، ولا يهم كل شيء ، فهو ببساطة يرسل إلى جميع المشتركين ما وصل إليه وينسى ذلك.


ReplaySubject - ولكن هذا هو الأهم ، عند إنشائه ، نقول له حجم المخزن المؤقت (عدد القيم التي سيتم تذكرها) ، ونتيجة لذلك ، فإنه يخزن قيم n الأخيرة في الذاكرة ويرسلها على الفور إلى المشترك الجديد.


 let subject = ReplaySubject<Int>.create(bufferSize: 3) subject.subscribe { (event) in print("  \(event)") } subject.onNext(1) subject.subscribe { (event) in print("  \(event)") } subject.onNext(2) subject.onNext(3) subject.subscribe { (event) in print("  \(event)") } subject.onNext(4) 

نحن ننظر إلى وحدة التحكم


   next(1)   next(1)   next(2)   next(2)   next(3)   next(3)   next(1)   next(2)   next(3)   next(4)   next(4)   next(4) 

BehaviorSubject ليس هراء مثل السابق ، فهو يحتوي على قيمة البدء ويتذكر القيمة الأخيرة ويرسلها فورًا بعد اشتراك المشترك.


 let subject = BehaviorSubject<Int>(value: 0) subject.subscribe { (event) in print("  \(event)") } subject.onNext(1) subject.subscribe { (event) in print("  \(event)") } subject.onNext(2) subject.onNext(3) subject.subscribe { (event) in print("  \(event)") } subject.onNext(4) 

وحدة التحكم


   next(0)   next(1)   next(1)   next(2)   next(2)   next(3)   next(3)   next(3)   next(4)   next(4)   next(4) 

الخلاصة


كان هذا مقالًا تمهيديًا مكتوبًا بحيث تعرف الأساسيات ويمكنك البناء عليها لاحقًا. في المقالات التالية ، سنلقي نظرة على كيفية العمل مع RxSwift مع مكونات واجهة مستخدم iOS ، وإنشاء ملحقات لمكونات واجهة المستخدم.


لا RxSwift'om متحد


لا يتم تنفيذ البرمجة التفاعلية فقط في مكتبة RxSwift ، فهناك العديد من عمليات التنفيذ ، وهنا أكثرها شيوعًا ReacktiveKit / Bond و ReactiveSwift / ReactiveCocoa . لديهم جميعًا اختلافات طفيفة في التنفيذ تحت غطاء المحرك ، ولكن في رأيي ، من الأفضل أن تبدأ معرفتك بالتفاعلية مع RxSwift ، حيث أنها الأكثر شيوعًا بينهم وستكون هناك المزيد من الإجابات في Google الرائعة ، ولكن بعد ذلك فهم جوهر هذا المفهوم ، يمكنك اختيار المكتبات حسب ذوقك ولونك.
كاتب المقالة: بافيل Grechikhin

Source: https://habr.com/ru/post/ar423603/


All Articles