RXSWIFT BY EXAMPLES #4 – MULTITHREADING - PART I

Theo tài liệu: Droids Khi chúng ta nói về Rx, hầu hết thời gian là để nói về việc connecting (binding) dữ liệu với UI. Bạn có thể thấy điều đó rất rõ ràng trong các phần trước. Tuy nhiên cũng có 1 phần quan trọng không kém là lấy dữ liệu về: retrieving data. Khi get dữ liệu từ server về, việc chính của chúng ta là parse nó về object. Nếu data đủ lớn, việc mapping có thể là 1 vấn đề không nhỏ với bộ nhớ và tốn thời gian, đặc biệt khi nó chạy trên main thread, chúng ta sẽ phải block thread UI lại, và như vậy sẽ dẫn đến một UX tồi.

Ở phần 3 của series này, chúng ta đã mapping object. Và chúng ta đã sử dụng MainScheduler.instance trong 1 số operators, bở vì chúng ta muốn chắc chắn data được ở trên main thread. Thực tế, đây là 1 Scheduler, ko phải Thread. vậy tại sao chúng ta lại nói về thread? Hơn nữa, chúng ta đã phân tích rằng: ko nên map objects ở trên main thread, nhưng có vẻ như chúng ta đã làm điều đó ở phần 3, vậy thực sự điều gì đang xảy ra?

Schedulers

Chúng ta sẽ bắt đầu với 1 chút lý thuyết về chedulers. Khi chúng ta xử lý các operations với Rx, trên định nghĩa chúng đều thực hiện trên cùng 1 thread. Tức là: trừ khi bạn thay đổi thread 1 cách thủ công, nếu không thì điểm bắt đầu vào của chuỗi (entry point of chain) bắt đầu trên thread nào thì nó sẽ dispose ở cùng thread đó. Schedulers không thực sự là threads, nhưng giống như cái tên, nó sẽ lên lịch cho các tasks mà nó nhận được. Chúng ta có 2 kiểu schedulers: serial và concurrent. Những kiểu schedulers có sẵn là:

  • CurrentThreadScheduler (Serial): schedule trên curent thread, đây cũng là default schedule
  • MainScheduler (Serial): schedule trên main thread
  • SerialDispatchQueueScheduler (Serial): schedule trên một queue cụ thể (dispatch_queue_t)
  • ConcurrentDispatchQueueScheduler (Concurrent): schedule trên một queue cụ thể (dispatch_queue_t)
  • OperationQueueScheduler (Concurrent): schedule trên một queue cụ thể (NSOperationQueue) Một điều thú vị đó là: nếu bạn truyền 1 concurrent queue tới một serial scheduler, RxSwift chắc chắn sẽ transform nó thành serial queue. Theo chiều ngược lại, (truyền serial queue tới concurrent scheduler) sẽ không gây ra vấn đề gì cả, nhưng chúng ta vẫn nên tránh nó, nếu có thế.

observeOn() & subscribeOn()

2 methods là cốt lõi cho việc multithreading. Khi bạn nhìn vào, bạn sẽ thấy tên của chúng rất rõ ràng cho việc chúng sẽ làm gì. Tuy nhiên, trên thực tế, rất ít người hiểu đc sự khác biệt giữa chúng và 1 số hành vi cụ thể xảy ra với việc sử dụng chúng. Nhưng hãy quên nó đi một chúng, chúng tôi nhận được cuộc gọi từ 1 người bạn và chúng ta đang lên đường. Bạn của chúng ta, Emily nhờ chúng ta chăm sóc cho con mèo của cô là Ethan, trong thời gian cô ấy đi nghỉ mát. Cô ấy trở về và chúng ta phải mang Ethan trả về. Chúng ta phải phải lái xe tới nhà cô ấy, vì vậy hãy chuẩn bị cho chuyến đi vui vẻ này. Thông thường, khi lái xe tới nàh Emily, chúng ta sẽ chọn đường thông thường là đi qua xa lộ. Nhưng hôm nay, chúng ta muống thay đổi 1 chút, vì thế chúng ta chọn đường khác (cứ cho là đường cao tốc phân làn đi). Thời tiết đẹp vcl, và để đỡ có lỗi với thời tiết, sau 1 tiếng lái xe, chúng ta dừng lại để hít thở chút không khí. Đột nhiên chúng ta chợt nhận ra lái xe trong thời tiết như thế này phải ở đường xa lộ nó mới sướng, và vì thế chúng ta chuyển về xa lộ. Với một chút nhạc nhẽo, một chú mèo ngoan, và thời tiết đẹp cùng với vài giờ lái xe, chúng ta đã đến nhà Emily, trả con mèo và mọi người đều vui vẻ. Và chúng ta vừa mới học về observeOn() & subscribeOn()

Làm rõ vấn đề trên: chúng ta là một Observable, Ethan là Signal mà chúng ta tạo ra, tuyến đường chúng ta đi là Scheduler và Emily là một Observer. Emily đăng ký việc theo dõi chúng ta (subscribes to us), và cô ấy tin rằng cô ấy sẽ nhận được 1 tín hiệu mới (1 con mều). Chúng ta có 1 tuyến đường mặc định khi chúng ta lái xe tới chỗ của Emily. Tương tự như với Rx, chúng ta cũng luôn có 1 default scheduler. Nhưng lần này, chúng ta chọn 1 tuyến đường khác (scheduler) làm điểm khởi hành. Khi bạn muốn bắt đầu chuyến đi của mình với 1 tuyến đường khác với tuyến default, bạn sẽ sử dụng method subscribeOn(). Đây là nhược điểm: Nếu bạn sử dụng subscribeOn() bạn sẽ ko thể căhcs chắn rằng đến cuối chuyến đi (subscibeNext() được sử dụng bởi Emily) bạn sẽ vẫn còn ở trên con đường mà bạn đã bắt đầu. Bản chỉ đảm bảo rằng bạn sẽ bắt đầu đi từ đó.

Method thứ 2, observeOn() cũng có thể thay đổi con đường của bạn. Nhưng nó không bị hạn chế hay rằng buộc bởi điểm khởi đầu của chuyến đi, bạn có thể chuyển tuyến đường bằng cách sử dụng observeOn() bất cứ lúc nào trong cuộc hành trình. Để so sánh, subscribeOn() chỉ có thể chuyển tuyến đường ngay từ ban đầu - đó là sự khác biệt. Hầu hết bạn sẽ sử dụng observeOn().

Trở lại công việc trả mèo, Pseudo-code sau sẽ thể hiện việc giao mều của chúng ta trong RxSwift như sau:

catObservable // 1
    .breatheFreshAir() // 2
    .observeOn(MainRouteScheduler.instance) // 3
    .subscribeOn(TwoLaneFreewayScheduler.instance) // 4
    .subscribeNext { cat in // 5
        if cat is Ethan {
            hug(cat)
        }
    }
    .addDisposableTo(disposeBag)
  1. Chúng ta đăng ký việc theo dõi tới catObservable, mà nó sẽ phát ra một Cat signals
  2. Chúng ta ở trên cùng schedule mà chúng ta đang ở trước khi subscription (mà là 1 hành vi default của Rx)
  3. Chuyển scheduler sang MainRouteScheduler. Bây giờ tất cả các operation theo sau cái này sẽ được scheduled trên MainRouteScheduler (đươgn nhiên nêú chúng ta không chuyển scheduler thêm lần nữa trong pipeline).
  4. Bây giờ chúng ta nói rằng chúng ta bắt đầu chuỗi (chain) trên TwoLaneFreewayScheduler. Vì vậy breatheFreshAir() sẽ được scheduled trên TwoLaneFreewayScheduler, và sau đó scheduler lại chuyển lại do sử dụng observeOn().
  5. subsribeNext() được scheduled bởi MainRouteScheduler. Nếu chúng ta khong add observeOn() trước đó, nó sẽ ị scheduled bởi TwoLaneFreewayScheduler.

Tổng hợp lại: subsribeOn() trỏ vào điểm bắt đầu của cả chuỗi, observeOn() trỏ vào nơi để đi tiếp. Bức ảnh dưới đây (courtesy of reactivex.io) sẽ rất rõ ràng về việc điều gì xảy ra khi nhưgnx methods này được gọi. Chú ý rằng các mũi tên màu xanh da trời thể hiện cho subscribeOn() scheduler và mũi tên màu da cam và hồng thể hiện cho 2 schedulers khác nhau được dùng khi mỗi observerOn() được gọi.

Example:

Ở trong Phần 3 , chúng ta đã làm công việc : tìm kiếm các issues cho một repository trên GibHub. Hôm nay chúng ta sẽ tìm kiếm repository theo username, vẫn trên GibHub. Lần này, ta sẽ sử dụng Alamofire cho việc request network và ObjectMapper cho việc parsing objects. Đầu tiên, hãy tạo 1 project như tutorial trước đó, chúng ta sử dụng CocoaPods với RxAlamofire, file pod sẽ như sau:

platform :ios, '8.0'
use_frameworks!
 
target 'RxAlamofireExample' do
 
pod 'RxAlamofire/RxCocoa'
pod 'ObjectMapper'
 
end

Tới phần coding, với phương châm "think before code" chugns ta cần phải nghĩ đến cái cấu trúc code trước: chúng ta cần phải làm gì và cách thực hiện như thế nào. Tron trường này, outline của chúng ta sẽ như sau:

  1. Tạo UI: gồm 1 UISearchBarUITableView
  2. Observe search bar và mỗi khi 1 value mới nhận được, transform nó thành array của các repositories (nếu có thể). Ở đây chúng ta sẽ cần thêm model cho việc requests network
  3. Update tableview với data mới. Chúng ta cần phải tính tới schedulers và làm sao để ko làm ảnh hưởng đến UI. Việc code cụ thể sẽ tiếp tục trong phần II.