MVVM架構優點及應用場景

RxSwift應用

響應式編程 && 函數式編程

什麼是響應式編程?

響應式編程,響應式編程是一種面向數據流和變化傳播的編程方式式,簡單理解就是異步的數據流的開發。html

什麼是函數式編程?

特色是將函數做爲一等公民,看成參數和返回值使用。典型的如OC和Swift 中的 map函數、filter函數、reduce函數等。每一個函數的處理結果給到下一個函數,最後的結果由自身函數調出。git


爲何要使用 RxSwift ?

咱們先看一下 RxSwift 可以幫助咱們作些什麼:

Target Action

傳統實現方法:github

button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)

func buttonTapped() {
    print("button Tapped")
}
複製代碼

經過 Rx 來實現:編程

button.rx.tap
    .subscribe(onNext: {
        print("button Tapped")
    })
    .disposed(by: disposeBag)
複製代碼

你不須要使用 Target Action,這樣使得代碼邏輯清晰可見。swift


代理

傳統實現方法:緩存

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        scrollView.delegate = self
    }
}

extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        print("contentOffset: \(scrollView.contentOffset)")
    }
}
複製代碼

經過 Rx 來實現:bash

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()

        scrollView.rx.contentOffset
            .subscribe(onNext: { contentOffset in
                print("contentOffset: \(contentOffset)")
            })
            .disposed(by: disposeBag)
    }
}
複製代碼

你不須要書寫代理的配置代碼,就能得到想要的結果。markdown


閉包回調

傳統實現方法:網絡

URLSession.shared.dataTask(with: URLRequest(url: url)) {
    (data, response, error) in
    guard error == nil else {
        print("Data Task Error: \(error!)")
        return
    }

    guard let data = data else {
        print("Data Task Error: unknown")
        return
    }

    print("Data Task Success with count: \(data.count)")
}.resume()
複製代碼

經過 Rx 來實現:閉包

URLSession.shared.rx.data(request: URLRequest(url: url))
    .subscribe(onNext: { data in
        print("Data Task Success with count: \(data.count)")
    }, onError: { error in
        print("Data Task Error: \(error)")
    })
    .disposed(by: disposeBag)
複製代碼

回調也變得十分簡單


通知

傳統實現方法:

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(updateNotificationStatus), 
                                                     name: UIApplication.willEnterForegroundNotification,
                                                   object: nil)
}

deinit {
    NotificationCenter.default.removeObserver(self)
}

// MARK: - Notification
@objc func updateNotificationStatus(){
    print("Application Will Enter Foreground")
}
複製代碼

經過 Rx 來實現:

override func viewDidLoad() {
    super.viewDidLoad()

    NotificationCenter.default.rx.notification(UIApplication.willEnterForegroundNotification)
    .subscribe(onNext: { (notification) in
        print("Application Will Enter Foreground")
    }).disposed(by: disposeBag)
}
複製代碼

你不須要去管理觀察者的生命週期,這樣你就有更多精力去關注業務邏輯。


KVO

傳統實現方法:

private var observerContext = 0

override func viewDidLoad() {
    super.viewDidLoad()
    user.addObserver(self, forKeyPath: #keyPath(User.name), options: [.new, .initial], context: &observerContext)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if context == &observerContext {
        let newValue = change?[.newKey] as? String
        print("do something with newValue")
    } else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }
}

deinit {
    user.removeObserver(self, forKeyPath: #keyPath(User.name))
}
複製代碼

經過 Rx 來實現:

override func viewDidLoad() {
    super.viewDidLoad()

    user.rx.observe(String.self, #keyPath(User.name))
        .subscribe(onNext: { newValue in
            print("do something with newValue")
        })
        .disposed(by: disposeBag)
}
複製代碼

這樣實現 KVO 的代碼更清晰,更簡潔而且更準確。


多個任務之間有依賴關係

例如,先經過用戶名密碼取得 Token 而後經過 Token 取得用戶信息,

傳統實現方法:

/// 用回調的方式封裝接口
enum API {

    /// 經過用戶名密碼取得一個 token
    static func token(username: String, password: String, success: (String) -> Void,
        failure: (Error) -> Void) { ... }

    /// 經過 token 取得用戶信息
    static func userinfo(token: String, success: (UserInfo) -> Void,
        failure: (Error) -> Void) { ... }
}
複製代碼
/// 經過用戶名和密碼獲取用戶信息
API.token(username: "beeth0ven", password: "987654321",
    success: { token in
        API.userInfo(token: token,
            success: { userInfo in
                print("獲取用戶信息成功: \(userInfo)")
            },
            failure: { error in
                print("獲取用戶信息失敗: \(error)")
        })
    },
    failure: { error in
        print("獲取用戶信息失敗: \(error)")
})
複製代碼

經過 Rx 來實現:

/// 用 Rx 封裝接口
enum API {

    /// 經過用戶名密碼取得一個 token
    static func token(username: String, password: String) -> Observable<String> { ... }

    /// 經過 token 取得用戶信息
    static func userInfo(token: String) -> Observable<UserInfo> { ... }
}
複製代碼
/// 經過用戶名和密碼獲取用戶信息
API.token(username: "beeth0ven", password: "987654321")
    .flatMapLatest(API.userInfo)
    .subscribe(onNext: { userInfo in
        print("獲取用戶信息成功: \(userInfo)")
    }, onError: { error in
        print("獲取用戶信息失敗: \(error)")
    })
    .disposed(by: disposeBag)
複製代碼

這樣你無需嵌套太多層,從而使得代碼易讀,易維護。


等待多個併發任務完成後處理結果

例如,須要將兩個網絡請求合併成一個,

經過 Rx 來實現:

/// 用 Rx 封裝接口
enum API {

    /// 取得老師的詳細信息
    static func teacher(teacherId: Int) -> Observable<Teacher> { ... }

    /// 取得老師的評論
    static func teacherComments(teacherId: Int) -> Observable<[Comment]> { ... }
}
複製代碼
/// 同時取得老師信息和老師評論
Observable.zip(
      API.teacher(teacherId: teacherId),
      API.teacherComments(teacherId: teacherId)
    ).subscribe(onNext: { (teacher, comments) in
        print("獲取老師信息成功: \(teacher)")
        print("獲取老師評論成功: \(comments.count) 條")
    }, onError: { error in
        print("獲取老師信息或評論失敗: \(error)")
    })
    .disposed(by: disposeBag)
複製代碼

這樣你可用寥寥幾行代碼來完成至關複雜的異步操做。


RxSwift 的單向數據流

RxSwift 能夠在 UniDirectional Data Flow 的各個階段都發揮做用,從而讓 Data 的處理和流動更加簡潔和清晰。

image

  • 經過對 RxCocoa 的各類回調進行統一處理,方便了"交互"「Interact」的處理。
  • 經過對 Observable 的 transform 和 composite,方便了 Action 的生成(好比使用 throttle 來壓縮 Action)。
  • 經過對網絡請求以及其餘異步數據的獲取進行 Observable 封裝,方便了異步數據的處理。
  • 經過 RxCocoa 的 binding,方便了數據的渲染。

RxSwift優點

  • 組合 - Rx對不一樣的信號進行組合
  • 複用 - 由於它是可組合的
  • 清晰 - 由於聲明都是不可變的,改變的只有數據
  • 易用 - 由於它抽象的了異步編程,使咱們統一了代碼風格
  • 內存回收 - 簡單內存管理
  • 穩定 - 由於 Rx 是徹底經過單元測試的

RxSwift 與 MVVM的邂逅

MVVM 是 Model-View-ViewModel 的縮寫。

  • MVVM 增長了 ViewModel 層。咱們能夠將原來 Controller 中的業務邏輯抽取出來放到 ViewModel 中,從而大大減輕了 ViewController 的負擔。
  • 同時在 MVVM 中,ViewController 只擔任 View 的角色(ViewController 與 View 如今共同做爲 View 層),負責 View 的顯示和更新,其餘業務邏輯再也不須要 ViewController 來管了。

一樣使用 MVVM 架構時,Model 與 View|ViewControllter 之間是不容許直接通訊的,而是由 ViewModel 層進行協調

image

基於RxSwift對網絡工具封裝 TWSwiftHttpTool

// 聲明一個枚舉,包含成功和失敗的狀況
enum TWSwiftHttpResult {
    case success(Any)  //成功
    case failure(String) //失敗
    case noNet() //無網絡
}
    
    
// 使用RxSwift進行擴展
extension Reactive where Base: TWSwiftHttpTool {
    
    /// 基於YYCache 緩存的RXSwift 請求方式
    static func request(type: TWRequestType,
                        url: String,
                        parameters: [AnyHashable: Any]?,
                        flag: Bool = true,
                        isCache: Bool = false,
                        cacheKey: String? = nil,
                        cacheBlock: (CacheBlock)? = nil) -> Observable<Any> {
        return Observable.create { observer in
            let task = TWSwiftHttpTool.request(type: type, url: url, flag: flag, parameters: parameters, isCache: isCache, cacheKey: cacheKey, cacheBlock: cacheBlock, complete: { (result) in
                dealComplete(result: result, observer: observer)
            })
            return Disposables.create(with: task.cancel)
        }
    }
    
    
    
    // MARK: - Private Common Method
    private static func dealComplete(result:TWSwiftHttpResult,observer:AnyObserver<Any>) {
        switch result {
        case .success(let response):
            observer.onNext(response)
            observer.onCompleted()
        case .failure(let reason):
//            observer.onNext([TWSwiftErrorMsg:reason])
            observer.onError(RxNetworkError.general(reason))
        case .noNet:
//            observer.onNext([TWSwiftErrorMsg:TWSwiftNoNetMsg])
            observer.onError(RxNetworkError.noNet)
        }
    }
}

複製代碼

viewModel 定義

//訂閱輸入輸出協議
protocol TWSwiftViewModelProtocol {
    associatedtype TWSwiftInput
    associatedtype TWSwiftOutput
    
    func transform(input: TWSwiftInput) -> TWSwiftOutput
}

//viewModel實現TWSwiftViewModelProtocol的協議
extension NewHouseWeekViewModel:TWSwiftViewModelProtocol{
    
    typealias TWSwiftInput = Input
    
    typealias TWSwiftOutput = Output
    
    // MARK: - Override Method
    struct Input {
        //便是訂閱又是被訂閱
        let requestId = PublishSubject<String>()
    }
    
    struct Output {
        //輸出數據源
        let sections: Driver<[NewHouseWeekSectionModel]>
        //成功輸出
        let successSubject = PublishSubject<String>()
        //錯誤輸出
        let errorSubject = PublishSubject<String>()

        init(sections: Driver<[NewHouseWeekSectionModel]>) {
            self.sections = sections
        }
    }
    
    func transform(input: NewHouseWeekViewModel.Input) -> NewHouseWeekViewModel.Output {
        ```
    }
}
複製代碼

viewController經過viewModel實現model與view綁定

/// viewModel綁定
    fileprivate func bindViewModel() {
        let vmInput = NewHouseWeekViewModel.Input() ///輸入
        let vmOutput = viewModel.transform(input: vmInput) ///輸出
        ///數據源綁定到tableview中
        vmOutput.sections.asDriver().drive(tableView.rx.items(dataSource: dataSource)).disposed(by: TWSwiftDisposeBag)
        ///錯誤訂閱
        vmOutput.errorSubject.subscribe(onNext:{[weak self] (errorMsg) in
            self?.hideHud(in: self?.tableView, hint: errorMsg)
            self?.setEmptyDic()
        }).disposed(by: TWSwiftDisposeBag)
        ///成功訂閱
        vmOutput.successSubject.subscribe(onNext: nil, onError: nil, onCompleted: {[weak self] in
            self?.hideHUD()
            self?.setEmptyDic()
        }, onDisposed: nil).disposed(by: TWSwiftDisposeBag)
        let regionId = TWSwiftGuardNullString(GlobalObject.share()?.regionId)
        ///發起數據請求
        vmInput.requestId.onNext(regionId)
        self.showActivityIndicatorSuperView(tableView)
    }
複製代碼

RxSwift的見解

理解響應鏈的編程思路 RxSwift給咱們帶來最大影響的Reactive思想,OOP告訴咱們,在編寫應用程序的時候,要考慮的是對象有什麼,對象作什麼,對象與對象之間的聯繫,而Reactive思想將對象所作的都當作是數據流,咱們關注的是事件自己的影響。

參考連接

相關文章
相關標籤/搜索