1. 程式人生 > >RxSwift 專案實戰記錄

RxSwift 專案實戰記錄

點選上方“iOS開發”,選擇“置頂公眾號”

關鍵時刻,第一時間送達!

640?wxfrom=5&wx_lazy=1

0.gif?wxfrom=5&wx_lazy=1

最近剛剛把接手的OC專案搞定,經過深思熟慮後,本人決定下個專案起就使用Swift(學了這麼久的Swift還沒真正用到實際專案裡。。。),而恰巧RxSwift已經出來有一些時間了,語法也基本上穩定,遂隻身前來試探試探這RxSwift,接著就做了個小Demo,有興趣的同學可以瞧一瞧~

0.gif?wxfrom=5&wx_lazy=1

Exhibition

結構

├── Controller

│   └── LXFViewController.swift     // 主檢視控制器

├── Extension

│   └── Response+ObjectMapper.swift // Response分類,Moya請求完進行Json轉模型或模型陣列

├── Model

│   └── LXFModel.swift              // 模型

├── Protocol

│   └── LXFViewModelType.swift      // 定義了模型協議

├── Tool

│   ├── LXFNetworkTool.swift        // 封裝Moya請求

│   └── LXFProgressHUD.swift        // 封裝的HUD

├── View

│   ├── LXFViewCell.swift           // 自定義cell

│   └── LXFViewCell.xib             // cell的xib檔案

└── ViewModel

    └── LXFViewModel.swift          // 檢視模型

第三方庫

RxSwift         // 想玩RxSwift的必備庫

RxCocoa         // 對 UIKit Foundation 進行 Rx 化

NSObject+Rx     // 為我們提供 rx_disposeBag 

Moya/RxSwift    // 為RxSwift專用提供,對Alamofire進行封裝的一個網路請求庫

ObjectMapper    // Json轉模型之必備良品

RxDataSources   // 幫助我們優雅的使用tableView的資料來源方法

Then            // 提供快速初始化的語法糖

Kingfisher      // 圖片載入庫

SnapKit         // 檢視約束庫

Reusable        // 幫助我們優雅的使用自定義cell和view,不再出現Optional

MJRefresh       // 上拉載入、下拉重新整理的庫

SVProgressHUD   // 簡單易用的HUD

敲黑板

Moya的使用

Moya是基於Alamofire的網路請求庫,這裡我使用了Moya/Swift,它在Moya的基礎上添加了對RxSwift的介面支援。接下來我們來說下Moya的使用

一、建立一個列舉,用來存放請求型別,這裡我順便設定相應的路徑,等下統一取出來直接賦值即可

enum LXFNetworkTool {

    enum LXFNetworkCategory: String {

        case all     = "all"

        case android = "Android"

        case ios     = "iOS"

        case welfare = "福利"

    }

    case data(type: LXFNetworkCategory, size:Int, index:Int)

}

二、為這個列舉寫一個擴充套件,並遵循塄 TargetType,這個協議的Moya這個庫規定的協議,可以按住Commond鍵+單擊左鍵進入相應的檔案進行檢視

extension LXFNetworkTool: TargetType {

    /// baseURL 統一基本的URL

    var baseURL: URL {

        return URL(string: "http://gank.io/api/data/")!

    }

    /// path欄位會追加至baseURL後面

    var path: String {

        switch self {

        case .data(let type, let size, let index):

            return "(type.rawValue)/(size)/(index)"

        }

    }

    /// HTTP的請求方式

    var method: Moya.Method {

        return .get

    }

    /// 請求引數(會在請求時進行編碼)

    var parameters: [String: Any]? {

        return nil

    }

    /// 引數編碼方式(這裡使用URL的預設方式)

    var parameterEncoding: ParameterEncoding {

        return URLEncoding.default

    }

    /// 這裡用於單元測試,不需要的就像我一樣隨便寫寫

    var sampleData: Data {

        return "LinXunFeng".data(using: .utf8)!

    }

    /// 將要被執行的任務(請求:request 下載:upload 上傳:download)

    var task: Task {

        return .request

    }

    /// 是否執行Alamofire驗證,預設值為false

    var validate: Bool {

        return false

    }

}

三、定義一個全域性變數用於整個專案的網路請求

let lxfNetTool = RxMoyaProvider<LXFNetworkTool>()

至此,我們就可以使用這個全域性變數來請求資料了

RxDataSources

如果你想用傳統的方式也行,不過這就失去了使用RxSwift的意義。好吧,我們接下來說說如何優雅的來實現tableView的資料來源。其實RxDataSources官網上已經有很明確的使用說明,不過我還是總結一下整個過程吧。

概念點

RxDataSources是以section來做為資料結構來傳輸,這點很重要,可能很多同學會比較疑惑這句話吧,我在此舉個例子,在傳統的資料來源實現的方法中有一個numberOfSection,我們在很多情況下只需要一個section,所以這個方法可實現,也可以不實現,預設返回的就是1,這給我們帶來的一個迷惑點:【tableView是由row來組成的】,不知道在坐的各位中有沒有是這麼想的呢??有的話那從今天開始就要認清楚這一點,【tableView其實是由section組成的】,所以在使用RxDataSources的過程中,即使你的setion只有一個,那你也得返回一個section的陣列出去!!!

一、自定義Section

在我們自定義的Model中建立一個Section的結構體,並且建立一個擴充套件,遵循SectionModelType協議,實現相應的協議方法。約定俗成的寫法呢請參考如下方式

LXFModel.swift

struct LXFSection {

    // items就是rows

    var items: [Item]

    // 你也可以這裡加你需要的東西,比如 headerView 的 title

}

extension LXFSection: SectionModelType {

    // 重定義 Item 的型別為 LXFModel

    typealias Item = LXFModel

    // 實現協議中的方式

    init(original: LXFSection, items: [LXFSection.Item]) {

        self = original

        self.items = items

    }

}

二、在控制器下建立一個數據源屬性

以下程式碼均在 LXFViewController.swift 檔案中

// 建立一個數據源屬性,型別為自定義的Section型別

let dataSource = RxTableViewSectionedReloadDataSource<LXFSection>()

使用資料來源屬性繫結我們的cell

// 繫結cell

dataSource.configureCell = { ds, tv, ip, item in

    // 這個地方使用了Reusable這個庫,在LXFViewCell中遵守了相應的協議

    // 使其方便轉換cell為非可選型的相應的cell型別

    let cell = tv.dequeueReusableCell(for: ip) as LXFViewCell

    cell.picView.kf.setImage(with: URL(string: item.url))

    cell.descLabel.text = "描述: (item.desc)"

    cell.sourceLabel.text = "來源: (item.source)"

    return cell

}

三、將sections序列繫結給我們的rows

output.sections.asDriver().drive(tableView.rx.items(dataSource:dataSource)).addDisposableTo(rx_disposeBag)

大功告成,接下來說說section序列的產生

ViewModel的規範

我們知道MVVM思想就是將原本在ViewController的檢視顯示邏輯、驗證邏輯、網路請求等程式碼存放於ViewModel中,讓我們手中的ViewController瘦身。這些邏輯由ViewModel負責,外界不需要關心,外界只需要結果,ViewModel也只需要將結果給到外界,基於此,我們定義了一個協議LXFViewModelType

一、建立一個LXFViewModelType.swift

LXFViewModelType.swift

// associatedtype 關鍵字 用來宣告一個型別的佔位符作為協議定義的一部分

protocol LXFViewModelType {

    associatedtype Input

    associatedtype Output

    func transform(input: Input) -> Output

}

二、viewModel遵守LXFViewModelType協議

  1. 我們可以為XFViewModelType的Input和Output定義別名,以示區分,如:你這個viewModel的用於請求首頁模組相關聯的,則可以命名為:HomeInput 和 HomeOutput

   2.我們可以豐富我們的 Input 和 Output 。可以看到我為Output添加了一個序列,型別為我們自定義的LXFSection陣列,在Input裡面添加了一個請求型別(即要請求什麼資料,比如首頁的資料)

   3.我們通過 transform 方法將input攜帶的資料進行處理,生成了一個Output

注意: 以下程式碼為了方便閱讀,進行了部分刪減

LXFViewModel.swift

extension LXFViewModel: LXFViewModelType {

   // 存放著解析完成的模型陣列

   let models = Variable<[LXFModel]>([])

    // 為LXFViewModelType的Input和Output定義別名

    typealias Input = LXFInput

    typealias Output = LXFOutput

    // 豐富我們的Input和Output

    struct LXFInput {

        // 網路請求型別

        let category: LXFNetworkTool.LXFNetworkCategory

        init(category: LXFNetworkTool.LXFNetworkCategory) {

            self.category = category

        }

    }

    struct LXFOutput {

        // tableView的sections資料

        let sections: Driver<[LXFSection]>

        init(sections: Driver<[LXFSection]>) {

            self.sections = sections

        }

    }

    func transform(input: LXFViewModel.LXFInput) -> LXFViewModel.LXFOutput {

        let sections = models.asObservable().map { (models) -> [LXFSection] in

            // 當models的值被改變時會呼叫,這是Variable的特性

            return [LXFSection(items: models)] // 返回section陣列

        }.asDriver(onErrorJustReturn: [])

        let output = LXFOutput(sections: sections)

        // 接下來的程式碼是網路請求,請結合專案檢視,不然會不方便閱讀和理解

    }

}

接著我們在ViewController中初始化我們的input,通過transform得到output,然後將我們output中的sections序列繫結tableView的items

LXFViewController.swift

// 初始化input

let vmInput = LXFViewModel.LXFInput(category: .welfare)

// 通過transform得到output

let vmOutput = viewModel.transform(input: vmInput)

vmOutput.sections.asDriver().drive(tableView.rx.items(dataSource: dataSource)).addDisposableTo(rx_disposeBag)

RxSwift中使用MJRefresh

一、定義一個列舉LXFRefreshStatus,用於標誌當前重新整理狀態

enum LXFRefreshStatus {

    case none

    case beingHeaderRefresh

    case endHeaderRefresh

    case beingFooterRefresh

    case endFooterRefresh

    case noMoreData

}

二、在LXFOutput新增一個refreshStatus序列,型別為LXFRefreshStatus

// 給外界訂閱,告訴外界的tableView當前的重新整理狀態

let refreshStatus = Variable<LXFRefreshStatus>(.none)

我們在進行網路請求並得到結果之後,修改refreshStatus的value為相應的LXFRefreshStatus項

三、外界訂閱output的refreshStatus

外界訂閱output的refreshStatus,並且根據接收到的值進行相應的操作

vmOutput.refreshStatus.asObservable().subscribe(onNext: {[weak self] status in

    switch status {

    case .beingHeaderRefresh:

        self?.tableView.mj_header.beginRefreshing()

    case .endHeaderRefresh:

        self?.tableView.mj_header.endRefreshing()

    case .beingFooterRefresh:

        self?.tableView.mj_footer.beginRefreshing()

    case .endFooterRefresh:

        self?.tableView.mj_footer.endRefreshing()

    case .noMoreData:

        self?.tableView.mj_footer.endRefreshingWithNoMoreData()

    default:

        break

    }

}).addDisposableTo(rx_disposeBag)

四、output提供一個requestCommond用於請求資料

PublishSubject 的特點:即可以作為Observable,也可以作為Observer,說白了就是可以傳送訊號,也可以訂閱訊號

// 外界通過該屬性告訴viewModel載入資料(傳入的值是為了標誌是否重新載入)

let requestCommond = PublishSubject<Bool>()

在transform中,我們對生成的output的requestCommond進行訂閱

output.requestCommond.subscribe(onNext: {[unowned self] isReloadData in

    self.index = isReloadData ? 1 : self.index+1

    lxfNetTool.request(.data(type: input.category, size: 10, index: self.index)).mapArray(LXFModel.self).subscribe({ [weak self] (event) in

        switch event {

        case let .next(modelArr):

            self?.models.value = isReloadData ? modelArr : (self?.models.value ?? []) + modelArr

            LXFProgressHUD.showSuccess("載入成功")

        case let .error(error):

            LXFProgressHUD.showError(error.localizedDescription)

        case .completed:

            output.refreshStatus.value = isReloadData ? .endHeaderRefresh : .endFooterRefresh

        }

    }).addDisposableTo(self.rx_disposeBag)

}).addDisposableTo(rx_disposeBag)

五、在ViewController中初始化重新整理控制元件

為tableView設定重新整理控制元件,並且在建立重新整理控制元件的回撥中使用output的requestCommond發射訊號

tableView.mj_header = MJRefreshNormalHeader(refreshingBlock: { 

    vmOutput.requestCommond.onNext(true)

})

tableView.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: { 

    vmOutput.requestCommond.onNext(false)

})

總結流程:

  1. ViewController已經拿到output,當下拉載入資料的時候,使用output的requestCommond發射資訊,告訴viewModel我們要載入資料

   2.viewModel請求資料,在處理完json轉模型或模型陣列後修改models,當models的值被修改的時候會發訊號給sections,sections在ViewController已經繫結到tableView的items了,所以此時tableView的資料會被更新。接著我們根據請求結果,修改output的refreshStatus屬性的值

    3.當output的refreshStatus屬性的值改變後,會發射訊號,由於外界之前已經訂閱了output的refreshStatus,此時就會根據refreshStatus的新值來處理重新整理控制元件的狀態

好了,附上RxSwiftDemo地址:https://github.com/LinXunFeng/RxSwiftDemo。完結撒花

640?

  • 作者:handyTOOL

  • 連結:http://www.jianshu.com/p/31f6f10ce09a

  • 來源:簡書

  • iOS開發整理髮布,轉載請聯絡作者授權

640?