RxSwift bagian 1

ReactiveX logo


Selamat siang, Habrovsk. Dalam seri artikel ini, saya ingin berbicara tentang pemrograman reaktif, yaitu framework
RxSwift . Ada artikel tentang RxSwift di Habré dan di jaringan, tetapi, menurut saya, itu terlalu sulit untuk pemula. Karena itu, jika Anda mulai memahami pemrograman reaktif di iOS, maka saya minta cat.


Mari kita mulai dengan mendefinisikan apa itu pemrograman reaktif.


Pemrograman reaktif adalah paradigma pemrograman yang berfokus pada aliran data dan penyebaran perubahan.

Itulah yang disampaikan Wikipedia kepada kita.


Dengan kata lain, dalam kasus ketika kita memprogram dalam gaya imperatif, kita menulis dalam kode seperangkat perintah yang harus dieksekusi secara berurutan. Gaya pemrograman reaktif menganut beberapa konsep lain. Dengan gaya pemrograman reaktif, program kami adalah "pendengar" perubahan keadaan pada objek yang diamati. Kedengarannya rumit, tetapi tidak, cukup hanya dengan menembus konsep ini dan semuanya akan menjadi sangat mudah dan jelas, belum ada bug .


Saya tidak akan melukis cara menginstal kerangka kerja, mudah dilakukan dengan mengklik tautan . Ayo berlatih.


Diamati


Mari kita mulai dengan yang mudah diamati tetapi penting atau diamati. Diamati adalah apa yang akan memberi kita data, perlu untuk menghasilkan aliran data.


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

BINGO ! Kami menciptakan yang pertama diamati.


jadi apa?


Karena kita menciptakan objek yang diamati, logis bahwa kita perlu membuat objek yang akan mengamati.


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

kami mendapatkan yang berikut di log:


 next( observable) completed 

selesai?


Dapat diamati mengirimkan informasi tentang acara tersebut, hanya ada 3 jenis:


  • selanjutnya
  • kesalahan
  • selesai

Bersama dengan elemen berikutnya datang elemen yang kami kirim dan semua peristiwa yang dikirim oleh kami, kesalahan dikirim seperti namanya ketika terjadi kesalahan, dan diselesaikan dalam kasus ketika diamati kami telah mengirim semua data dan berakhir.


Kita bisa buat lebih detail pengamat pelanggan dan dapatkan tampilan yang lebih nyaman untuk menangani semua acara.


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

   finish disposed 

Dalam diamati, Anda dapat membuat urutan tidak hanya dari satu baris, dan memang tidak hanya dari baris, kita bisa memasukkan tipe data apa pun di sana.


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

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

Dapat diamati dapat dibuat dari berbagai nilai.


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

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

Satu diamati dapat memiliki sejumlah pelanggan . Sekarang terminologi, apa yang bisa diamati?


Dapat diamati adalah dasar dari semua Rx, yang secara asinkron menghasilkan urutan data yang tidak dapat diubah dan memungkinkan orang lain untuk berlangganan.


Membuang


Sekarang kita tahu cara membuat urutan dan berlangganan, kita perlu berurusan dengan hal seperti membuang .


Penting untuk diingat bahwa Observable adalah tipe "dingin", yaitu yang dapat diobservasi tidak "memancarkan" peristiwa apa pun sampai berlangganan. Teramati ada sampai mengirim pesan kesalahan atau pesan kesalahan ( selesai ). Jika kami ingin membatalkan langganan secara eksplisit, kami dapat melakukan hal berikut.


 // №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() 

Masih ada lagi cantik opsi yang benar.


 //  "" 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) 

Jadi, kami menambahkan langganan kami ke kantong daur ulang atau ke Buang Sampah .
Untuk apa ini? Jika Anda, menggunakan langganan, tidak menambahkannya ke tas Anda atau secara eksplisit tidak menelepon buang , atau, dalam kasus yang ekstrim, jangan membawa penyelesaian yang dapat diamati dengan cara apa pun, maka kemungkinan besar Anda akan mendapatkan kebocoran memori. Anda akan menggunakan DisposeBag sangat sering dalam pekerjaan Anda dengan RxSwift.


Operator


Pemrograman fungsional-reaktif (FRP di bawah) memiliki banyak operator bawaan untuk mengubah elemen yang dapat diamati. Ada situs rxmarbles , di sana Anda dapat melihat pekerjaan dan efek dari semua operator, well, tapi kami masih melihat beberapa dari mereka.


Peta


Operator peta sangat sering digunakan dan saya pikir itu sudah biasa bagi banyak orang, dengan bantuannya kami mengubah semua elemen yang diterima.
Contoh:


 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) 

Apa yang kita dapatkan di konsol:


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

Kami mengambil setiap elemen dari urutan dan membuat urutan hasil yang baru. Untuk memperjelas apa yang sedang terjadi, lebih baik untuk menulis lebih banyak.


 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) 

Apa itu $ 0?


$ 0 adalah nama elemen secara default, kita dapat menggunakan catatan yang disingkat dan penuh dalam metode, paling sering kita menggunakan catatan yang disingkat.


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

Setuju bahwa bentuk pendeknya jauh lebih sederhana, bukan?


Saring


Operator filter memungkinkan kita untuk menyaring data yang dipancarkan oleh kami yang dapat diamati, yaitu, ketika berlangganan, kami tidak akan menerima nilai yang tidak perlu bagi kami.
Contoh:


 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) 

Apa yang kita dapat di konsol?


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

Seperti yang kita lihat, di konsol kita hanya memiliki nilai-nilai yang memenuhi kondisi kita.


Ngomong-ngomong, operator bisa digabungkan, beginilah tampilannya jika kita ingin segera menerapkan operator filter dan operator peta .


 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) 

Konsol:


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

Berbeda


Operator hebat lainnya yang terkait dengan pemfilteran, operator yang berbeda memungkinkan Anda untuk hanya melewati data yang diubah, yang terbaik adalah segera beralih ke contoh dan semuanya akan menjadi jelas.


 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) 

Di konsol, kami mendapatkan yang berikut:


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

yaitu, jika elemen saat ini dari urutan identik dengan yang sebelumnya, maka itu dilewati dan seterusnya sampai elemen yang berbeda dari yang sebelumnya muncul, itu sangat nyaman ketika bekerja dengan, katakanlah, UI, yaitu tabel, jika kita menerima data sama seperti yang kita miliki sekarang, maka memuat ulang tabel seharusnya tidak.


Takelast


Operator takeLast yang sangat sederhana, kami mengambil elemen ke-n dari ujungnya.


 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) 

Di konsol kita mendapatkan yang berikut:


 next(6) completed 

Throttle dan Interval


Kemudian saya memutuskan untuk mengambil 2 operator sekaligus, hanya karena dengan bantuan operator kedua, mudah untuk menunjukkan pekerjaan yang pertama.


Operator throttle memungkinkan Anda untuk mengambil jeda antara menangkap nilai yang dikirimkan, ini adalah contoh yang sangat sederhana, Anda menulis aplikasi reaktif, menggunakan bilah pencarian dan tidak ingin memuat ulang tabel atau pergi ke server setiap kali Anda memasukkan data, jadi Anda menggunakan throttle dan dengan demikian mengatakan bahwa Apakah Anda ingin mengambil data pengguna setiap 2 detik (misalnya, Anda dapat mengatur interval apa pun) dan mengurangi konsumsi sumber daya untuk pemrosesan yang tidak perlu, bagaimana cara kerjanya dan dijelaskan dalam kode? Lihat di bawah untuk contoh.


 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) 

Di konsol akan menjadi:


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

Operator interval menyebabkan diamati untuk menghasilkan nilai setiap 0,5 detik dalam kenaikan 1 mulai dari 0, yang merupakan timer sederhana untuk Rx. Ternyata begitu nilai dihasilkan setiap 0,5 detik, maka 2 nilai dihasilkan per detik, aritmatika sederhana, dan operator throttle menunggu satu detik dan mengambil nilai terakhir.


Debounce


Debounce sangat mirip dengan pernyataan sebelumnya, tetapi sedikit lebih pintar menurut saya. Operator debounce menunggu jumlah waktu yang ke-n, dan jika tidak ada perubahan sejak dimulainya timer, ia mengambil nilai terakhir, jika kami mengirim nilainya, timer akan memulai kembali. Ini hanya sangat berguna untuk situasi yang dijelaskan dalam contoh sebelumnya, pengguna memasukkan data, kami menunggu ketika ia selesai (jika pengguna tidak aktif selama satu atau setengah detik), dan kemudian kami mulai melakukan beberapa tindakan. Karena itu, jika kita hanya mengubah operator dalam kode sebelumnya, maka kita tidak akan mendapatkan nilai-nilai ke konsol, karena debounce akan menunggu sebentar, tetapi setiap 0,5 detik akan menerima nilai baru dan memulai ulang timer-nya, jadi kita tidak akan mendapatkan apa-apa. Mari kita lihat sebuah contoh.


 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) 

Pada tahap ini, saya mengusulkan untuk menyelesaikan dengan operator, ada banyak dari mereka dalam kerangka kerja RxSwift, tidak dapat dikatakan bahwa semuanya sangat diperlukan dalam kehidupan sehari-hari, tetapi Anda masih perlu tahu tentang keberadaan mereka, jadi disarankan untuk membiasakan diri dengan daftar lengkap operator di situs web rxmarbles .


Penjadwal


Topik yang sangat penting yang ingin saya sentuh dalam artikel ini adalah penjadwal. Penjadwal, izinkan kami menjalankan pengamatan kami di utas tertentu dan mereka memiliki kehalusannya sendiri. Untuk memulai, ada 2 jenis pengaturan penjadwal yang bisa diamati - [observOn] () dan [subscribeOn] ().


Berlangganan


Berlangganan bertanggung jawab atas utas di mana seluruh proses yang dapat diobservasi akan berjalan sampai kejadiannya mencapai pengendali (pelanggan).


Amati


Seperti yang Anda tebak, observOn bertanggung jawab atas aliran acara yang diterima oleh pelanggan akan diproses.


Ini adalah hal yang sangat keren, karena kita dapat dengan mudah memasukkan unduhan sesuatu dari jaringan ke aliran latar belakang, dan ketika kita mendapatkan hasilnya, pergi ke aliran utama dan entah bagaimana bertindak pada UI.


Mari kita lihat bagaimana ini bekerja dengan sebuah contoh:


 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) }) 

Di konsol kita dapatkan:


 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) 

Kami melihat bahwa observable dibuat di utas latar belakang, dan kami memproses data di utas utama. Ini berguna saat bekerja dengan jaringan, misalnya:


 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)") } 

Dengan demikian, permintaan akan dieksekusi di utas latar belakang, dan semua pemrosesan respons akan terjadi di utama. Pada tahap ini, terlalu dini untuk mengatakan jenis metode rx URLSession tiba-tiba menarik, ini akan dibahas nanti, kode ini diberikan sebagai contoh menggunakan Penjadwal , omong-omong, kita akan mendapatkan respons berikut untuk konsol.


 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} 

Di akhir, mari kita lihat data apa yang datang kepada kita, untuk ini kita harus melakukan pemeriksaan agar tidak mulai mengurai pesan yang sudah selesai secara tidak sengaja.


 _ = 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)") } 

Kami memverifikasi bahwa acara tersebut bukan pesan penghentian yang dapat diobservasi atau kesalahan yang datang darinya, walaupun dimungkinkan untuk menerapkan metode berlangganan yang berbeda dan memproses semua jenis acara ini secara terpisah, tetapi Anda sudah dapat melakukan ini sendiri, dan kami akan mendapatkan yang berikut di konsol.


 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} 

Data diterima :-)


Subjek


Kita beralih ke yang panas, yaitu dari yang "dingin" atau "pasif" yang dapat diamati hingga yang "panas" atau "aktif", yang disebut subject'ami. Jika sebelum itu kami dapat mengamati mulai pekerjaan mereka hanya setelah berlangganan dan Anda memiliki pertanyaan di kepala Anda, "mengapa saya membutuhkan semua ini?", Kemudian Subjek selalu bekerja dan selalu mengirim data yang diterima.


Bagaimana itu? Dalam kasus yang bisa diamati, kami pergi ke klinik, pergi ke nenek jahat penerimaan ke meja resepsionis, mereka mendekati dan bertanya ke kantor mana kita harus pergi, kemudian granulasi menjawab kami, dalam hal subyek, granulasi berdiri dan mendengarkan jadwal dan kondisi dokter di rumah sakit dan segera setelah menerima informasi tentang pergerakan dokter mana pun, ia segera mengatakan ini, bertanya Sesuatu tidak berguna untuk granulasi, kita hanya bisa datang, mendengarkannya, pergi, dan dia akan terus berkata, sesuatu terbawa dengan perbandingan, mari kita ke kode.


Mari kita buat satu subjek dan 2 pelanggan, buat yang pertama tepat setelah subjek, kirim nilainya ke subjek, lalu buat yang kedua dan kirim beberapa nilai lagi.


 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) 

Apa yang akan kita lihat di konsol? benar, yang pertama berhasil mendapatkan acara pertama, tetapi yang kedua tidak.


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

Sudah lebih cocok dengan ide pemrograman reaktif Anda?
Subjek datang dalam beberapa bentuk, mereka semua berbeda dalam cara mereka mengirim nilai.


PublishSubject adalah yang paling sederhana, tidak masalah untuk semuanya, hanya mengirimkan ke semua pelanggan apa yang telah terjadi padanya dan melupakannya.


ReplaySubject - tetapi ini yang paling penting, saat membuat, kami katakan ukuran buffer (berapa banyak nilai yang akan diingat), sebagai hasilnya, ia menyimpan nilai n terakhir dalam memori dan mengirimkannya langsung ke pelanggan baru.


 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) 

Kami melihat konsol


   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 bukan omong kosong seperti yang sebelumnya, ia memiliki nilai awal dan mengingat nilai terakhir dan mengirimkannya segera setelah pelanggan berlangganan.


 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) 

Konsol


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

Kesimpulan


Ini adalah artikel pengantar yang ditulis sehingga Anda mengetahui dasar-dasarnya dan selanjutnya dapat membangunnya. Pada artikel berikut, kita akan melihat cara bekerja dengan RxSwift dengan komponen UI iOS, membuat ekstensi untuk komponen UI.


Bukan RxSwift'om bersatu


Pemrograman reaktif diimplementasikan tidak hanya di perpustakaan RxSwift, ada beberapa implementasi, berikut adalah yang paling populer di antaranya ReacktiveKit / Bond , ReactiveSwift / ReactiveCocoa . Mereka semua memiliki perbedaan kecil dalam implementasi di bawah tenda, tetapi menurut pendapat saya lebih baik memulai pengetahuan reaktivitas dengan RxSwift, karena ini adalah yang paling populer di antara mereka dan akan ada lebih banyak jawaban di Google yang hebat, tetapi setelah Anda memahami esensi dari konsep ini, Anda dapat memilih perpustakaan sesuai selera dan warna Anda.
Penulis artikel: Pavel Grechikhin

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


All Articles