12/08/2018, 17:08

Getting Started With RxSwift and RxCocoa: Networking

Tiếp theo các phần trước Phần 1 - Getting Started With RxSwift and RxCocoa, Phần 2 - Getting Started With RxSwift and RxCocoa : Observable and the Bind hôm nay chúng ta sẽ tiếp tục tìm hiểu về RxSwift, cụ thể là tìm hiểu về networking, cách lấy data và kết nối dữ liệu đó với View. Rx có rất nhiều ...

Tiếp theo các phần trước Phần 1 - Getting Started With RxSwift and RxCocoa, Phần 2 - Getting Started With RxSwift and RxCocoa : Observable and the Bind hôm nay chúng ta sẽ tiếp tục tìm hiểu về RxSwift, cụ thể là tìm hiểu về networking, cách lấy data và kết nối dữ liệu đó với View. Rx có rất nhiều networking extentions bao gồm RxAlamofire, Moya và ở trong bài viết này chúng ta sẽ tập trung ở Moya.

Moya là một abstract layer giúp cho chúng ta thực hiện các tác vụ về networking, về cơ bản bằng cách sử dụng thư viện này chúng ta có thể tạo kết nối tới API một cách đơn giản, với phần mở rộng bao gồm RxSwift vào ModelMapper chúng ta đã có đầy đủ vũ khí cho việc thực hiện kết nối mạng một cách ngon lành.

Để setup Moya chúng ta cần Provider thiết lập stubbing, endpoint closure ... Cho trường hợp của chúng ta chugns ta chỉ cần thiết lập đơn giản Provider với RxSwift. Việc thứ 2 chúng ta cần làm là thiết lập cấu hình Endpoint - 1 enum định nghĩa ra các API cần gọi. Việc này rất đơn giản, chúng ta chỉ cần tạo ra enum conform TargetType là xong, nó là 1 protocol gồm url, method, task, parameter và parameterEncoding. Param cuối cùng chúng ta cần specify là sampleData sử dụng cho việc fake data trả về và test.

Trong example này chúng ta sẽ lấy ra các issues của 1 reposity xác định sử dụng GitHub API. Trước tiên chúng ta lấy về các reposity, kiểm tra xem nó tồn tại hay ko, sau đó gửi request lấy về các issuses cho repository này. Chúng ta sẽ map kết quả trả về từ JSON sang objects, xử lý lỗi, xử lý dupplicating requests, spamming API ... Ví dụ của chúng ta sẽ chạy như sau:

Chúng ta sẽ tạo project với nội dung file pod như sau:

platform :ios, '8.0'
use_frameworks!
 
target 'RxMoyaExample' do
 
pod 'RxCocoa', '~> 3.0.0'
pod 'Moya-ModelMapper/RxSwift', '~> 4.1.0'
pod 'RxOptional'
 
end
 
post_install do |installer|
    installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
              config.build_settings['ENABLE_TESTABILITY'] = 'YES'
              config.build_settings['SWIFT_VERSION'] = '3.0'
        end
    end
end

Chúng ta sẽ bắt đầu với UI, ở đây UI chỉ đơn giản gồm 1 UITableview và 1 UISearchbar. Tiếp theo chúng ta cần1 viewcontroller để quản lý, nó sẽ lấy data từ search bar, pass cho model, model sẽ lấy dữ liệu từ server và đẩy nó cho tableview. Chúng ta sẽ tạo file IssueListViewController.swift nội dung như sau:

import Moya
import Moya_ModelMapper
import UIKit
import RxCocoa
import RxSwift
 
class IssueListViewController: UIViewController {
    
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var searchBar: UISearchBar!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupRx()
    }
    
    func setupRx() {
    }
}

Tiếp theo chúng ta sẽ tạo file GithubEndpoint.swift và tạo enum với 1 số target:

import Foundation
import Moya
 
enum GitHub {
    case userProfile(username: String)
    case repos(username: String)
    case repo(fullName: String)
    case issues(repositoryFullName: String)
}

enum Github như mình đã nói nó cần conform TargetType, cái này cũng chỉ đơn giản là 1 enum. Chúng ta sẽ tạo extension cho GitHub enum, nó sẽ gồm tất cả các thuộc tính cần thiết:

import Foundation
import Moya
 
private extension String {
    var URLEscapedString: String {
        return self.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlHostAllowed)!
    }
}
 
enum GitHub {
    case userProfile(username: String)
    case repos(username: String)
    case repo(fullName: String)
    case issues(repositoryFullName: String)
}
 
extension GitHub: TargetType {
    var baseURL: URL { return URL(string: "https://api.github.com")! }
    var path: String {
        switch self {
        case .repos(let name):
            return "/users/(name.URLEscapedString)/repos"
        case .userProfile(let name):
            return "/users/(name.URLEscapedString)"
        case .repo(let name):
            return "/repos/(name)"
        case .issues(let repositoryName):
            return "/repos/(repositoryName)/issues"
        }
    }
    var method: Moya.Method {
        return .get
    }
    var parameters: [String: Any]? {
        return nil
    }
    var sampleData: Data {
        switch self {
        case .repos(_):
            return "{{"id": "1", "language": "Swift", "url": "https://api.github.com/repos/mjacko/Router", "name": "Router"}}}".data(using: .utf8)!
        case .userProfile(let name):
            return "{"login": "(name)", "id": 100}".data(using: .utf8)!
        case .repo(_):
            return "{"id": "1", "language": "Swift", "url": "https://api.github.com/repos/mjacko/Router", "name": "Router"}".data(using: .utf8)!
        case .issues(_):
            return "{"id": 132942471, "number": 405, "title": "Updates example with fix to String extension by changing to Optional", "body": "Fix it pls."}".data(using: .utf8)!
        }
    }
    var task: Task {
        return .request
    }
    var parameterEncoding: ParameterEncoding {
        return JSONEncoding.default
    }
}

Chúng ta ko cần truyền bất kỳ param nào ở đây nên return nil, method luôn luôn là .get, baseURL cũng ko thay đổi, sampleData thì có trả về các dữ liệu khác nhau trong các trường hợp khác nhau do đó chúng ta để trong switch. Chúng ta đã thực thi xong Moya Provider. Chúng ta cũng cần thực hiện việc ẩn bàn phím khi click cell, việc này đương nhiên cũng được thực hiện với RxSwift, chúng ta sẽ cần 1 DisposeBag, thêm nữa chúng ta sẽ tạo ra 1 Observable mới để lấy dữ liệu text từ search bar, filter, ...

class IssueListViewController: UIViewController {
    ...
    let disposeBag = DisposeBag()
    var provider: RxMoyaProvider<GitHub>!    
    var latestRepositoryName: Observable<String> {
        return searchBar
            .rx.text
            .orEmpty
            .debounce(0.5, scheduler: MainScheduler.instance)
            .distinctUntilChanged()
    }
    ...
    func setupRx() {
        // First part of the puzzle, create our Provider
        provider = RxMoyaProvider<GitHub>()
   
        // Here we tell table view that if user clicks on a cell,
        // and the keyboard is still visible, hide it
        tableView
            .rx.itemSelected
            .subscribe(onNext: { indexPath in
                if self.searchBar.isFirstResponder() == true {
                    self.view.endEditing(true)
                }
            })
            .addDisposableTo(disposeBag)
    }
    ...
}

Chúng ta cần model để lấy dữ liệu từ text tương ứng lấy ở search bar, nhưng đầu tiên chúng ta cần parse object trước khi chúng ta truyền bất kì dữ liệu nào vào nó, việc này được thực hiện dễ dàng nhờ ModelMapper, chúng ta sẽ cần 2 entities, 1 cho Repository và 1 cho Issuse.

import Mapper
 
struct Repository: Mappable {
    
    let identifier: Int
    let language: String
    let name: String
    let fullName: String
    
    init(map: Mapper) throws {
        try identifier = map.from("id")
        try language = map.from("language")
        try name = map.from("name")
        try fullName = map.from("full_name")
    }
}
import Mapper
 
struct Issue: Mappable {
    
    let identifier: Int
    let number: Int
    let title: String
    let body: String
    
    init(map: Mapper) throws {
        try identifier = map.from("id")
        try number = map.from("number")
        try title = map.from("title")
        try body = map.from("body")
    }
}

Chúng ta ko cần quá nhiều thuộc tính, nếu bạn muốn có thể tự thêm thuộc tính dựa theo tài liệu GitHub API Tiếp theo chungs ta sẽ chuyển tới phần thú vị nhất của tutorial này, IssueTrackerModel - core of our Networking. Trước tiên, model của chúng ta cần có Provider property để pass nó vào hàm init. Sau đó chúng ta cần có property để observable text để lấy tên reposityNames

Let’s create the IssueTrackerModel.swift:

import Foundation
import Moya
import Mapper
import Moya_ModelMapper
import RxOptional
import RxSwift
 
struct IssueTrackerModel {
    
    let provider: RxMoyaProvider<GitHub>
    let repositoryName: Observable<String>
    
    func trackIssues() -> Observable<[Issue]> {
        
    }
    
    internal func findIssues(repository: Repository) -> Observable<[Issue]?> {
 
    }
    
    internal func findRepository(name: String) -> Observable<Repository?> {
 
    }
}

Chúng ta thêm 2 functions:

  • findRepository(            </div>
            
            <div class=
0