ReactiveX

I just finished the OC project I took over recently. After careful consideration, I decided to use Swift in the next project (I have learned Swift for so long but have not really applied it to the actual project…). , RxSwift has been out for some time, and the syntax is basically stable, so I came to test this RxSwift, and then made a small Demo, interested students can have a look ~





Exhibition

structure

. ├ ─ ─ Controller │ └ ─ ─ LXFViewController. Swift / / main view Controller ├ ─ ─ the Extension │ └ ─ ─ the Response + ObjectMapper. Swift / / Response classification, Moya finish requests to turn the Json array Model or Model ├ ─ ─ Model │ └ ─ ─ LXFModel. Swift / / Model ├ ─ ─ Protocol │ └ ─ ─ LXFViewModelType. Swift / / define the Model agreement ├ ─ ─ Tool │ ├ ─ ─ LXFNetworkTool. Swift / / encapsulation Moya request │ └ ─ ─ LXFProgressHUD. Swift / / encapsulated HUD ├ ─ ─ the View │ ├ ─ ─ LXFViewCell. Swift / / custom cell │ └ ─ ─ LXFViewCell. Xib / / cell xib file └ ─ ─ the ViewModel └ ─ ─ LXFViewModel. Swift/model/viewCopy the code

Third-party libraries

NSObject+Rx provides rx_disposeBag Moya/RxSwift // for UIKit Foundation Available exclusively for RxSwift, RxDataSources // help us use elegant tableView data source method Then // provide fast initialization syntax sugar Kingfisher // Image-loading library SnapKit // View constraint library Reusable // helps us gracefully use custom cells and Views, no more Optional MJRefresh // pull-up load, pull-down refresh libraries SVProgressHUD // Easy to use HUDCopy the code

On the blackboard

The use of Moya

Moya is a network request library based on Alamofire. Here I use Moya/Swift, which adds interface support for RxSwift on top of Moya. Now let’s talk about the use of Moya

Create an enumeration to store the request type, here I will set the corresponding path, etc

enum LXFNetworkTool { enum LXFNetworkCategory: String {case all = "all" case android = "android" case ios = "ios" case welfare = "welfare"} case data(type: LXFNetworkCategory, size:Int, index:Int) }Copy the code

Two, write an extension for this enumeration, and follow the Leng TargetType, this protocol of the Moya library provisions of the protocol, you can hold down the Commond key + click the left button to enter the corresponding file to view

Extension LXFNetworkTool: TargetType {var baseURL: URL {return URL(string: "http://gank.io/api/data/")! Var path: String {switch self {case. data(let type, let size, let index): Return "\(type.rawValue)/\(size)/\(index)"}} Moya.Method {return.get} var parameters: [String: Any]? Var parameterEncoding: {return nil} var parameterEncoding: ParameterEncoding {return urlencoding. default} ParameterEncoding {return urlencoding. default} Data { return "LinXunFeng".data(using: .utf8)! } /// the task to be executed (request: request: download: upload: download) var task: Task {return. Request} /// Whether to perform Alamofire validation. Default value: false var validate: Bool {return false}}Copy the code

Define a global variable for network requests for the entire project

let lxfNetTool = RxMoyaProvider<LXFNetworkTool>()
Copy the code

At this point, we can use this global variable to request data

RxDataSources

You can do it the old-fashioned way if you want, but that defeats the point of using RxSwift. Okay, so let’s talk about how to gracefully implement a datasource for tableView. The RxDataSources website has clear instructions for how to use it, but LET me summarize the process.

So RxDataSources is a data structure that transmits data in sections, and this is very important, and many of you might be confused by this, but in this example, in a traditional data source implementation we have a number of sections, and in many cases we only need one section, So this method can be implemented or not implemented, the default return is 1, which gives us a little bit of a trick: 【tableView is made up of rows 】, I don’t know if you think so. RxDataSources, even if you only have one setion, you have to return an array of sections!!

Create a Section structure in our custom Model and create an extension that follows the SectionModelType protocol and implements the corresponding protocol methods. Please refer to the following ways to write it

Lxfmodel. swift struct LXFSection {// items = rows var items: [Item] // You can also add what you need here, such as headerView title} extension LXFSection: SectionModelType {// Redefine Item type to LXFModel typeAlias Item = LXFModel init(original: LXFSection, items: [LXFSection.Item]) { self = original self.items = items } }Copy the code

Create a data source property under the controller

The following code is in the lxFViewController.swift file

/ / create a data source properties, type of custom Section type let dataSource = RxTableViewSectionedReloadDataSource < LXFSection > ()Copy the code

Bind our cell using data source properties

// Bind cell dataSource. ConfigureCell = {ds, TV, IP, item in // This place uses the Reusable library, 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 = "source: item. Source" return cell}Copy the code

Bind the SECTIONS sequence to our rows

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

So with that done, let’s talk about generating section sequences

The ViewModel specification

We know that the idea of MVVM is to store the ViewController’s display logic, validation logic, network requests and other code in the ViewModel, so that we can slim down the ViewController. This logic is handled by the ViewModel, the outside world doesn’t care, the outside world only needs the result, and the ViewModel only needs to give the result to the outside world. Based on this, we define a protocol, LXFViewModelType

Swift create lxFViewModelType.swift

Swift // AssociatedType The keyword is used to declare a placeholder for a type as part of the protocol definition protocol LXFViewModelType {associatedType Input associatedtype Output func transform(input: Input) -> Output }Copy the code

ViewModel complies with the LXFViewModelType protocol

  1. We can define aliases for XFViewModelType’s Input and Output to distinguish them. For example, your viewModel’s module associated with the request home page can be named HomeInput and HomeOutput
  2. We can enrich our Input and Output. As you can see, I added a sequence to the Output of our custom LXFSection array and a request type to the Input (what data to request, such as the home page data).
  3. We processed the data carried by the input through the transform method to generate an Output

Note: The following code has been partially deleted for ease of reading

LXFViewModel.swift extension LXFViewModel: Let models = Variable<[LXFModel]>([]) // Define alias typeAlias for LXFViewModelType Input and Output Input = LXFInput typealias Output = LXFOutput / / enrich our Input and Output struct LXFInput {/ / network request type category: LXFNetworkTool.LXFNetworkCategory init(category: LXFNetworkTool. LXFNetworkCategory) {self. The category = category}} struct LXFOutput {/ / let tableView sections of data sections: Driver<[LXFSection]> init(sections: Driver<[LXFSection]>) { self.sections = sections } } func transform(input: LXFViewModel.LXFInput) -> LXFViewModel.LXFOutput { let sections = models.asObservable().map { (models) -> [LXFSection] Return [LXFSection(items: Models)] // Return section array}. AsDriver (onErrorJustReturn: []) let output = LXFOutput(sections: sections)}}Copy the code

Then we initialize our input in the ViewController, get the output by transform, and bind the sections sequence in our Output to the items of the tableView

Swift // Initialize input let vmInput = lxfViewModel.lxfinput (category: .welfare) // get output let vmOutput = viewModel.transform(input: vmInput) vmOutput.sections.asDriver().drive(tableView.rx.items(dataSource: dataSource)).addDisposableTo(rx_disposeBag)Copy the code

MJRefresh is used in RxSwift

Define an enumeration, LXFRefreshStatus, to indicate the current refresh status

enum LXFRefreshStatus {
    case none
    case beingHeaderRefresh
    case endHeaderRefresh
    case beingFooterRefresh
    case endFooterRefresh
    case noMoreData
}
Copy the code

Add a refreshStatus sequence to LXFOutput of type LXFRefreshStatus

Let refreshStatus = Variable<LXFRefreshStatus>(.none)Copy the code

After making the network request and getting the result, we changed the value of refreshStatus to the corresponding LXFRefreshStatus item

RefreshStatus for output

The external world subscribes refreshStatus to the output and acts on the received value

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)Copy the code

Output provides a requestCommond for requesting data

PublishSubject can be either an Observable or an Observer. In other words, it can send signals or subscribe signals

// requestCommond = PublishSubject<Bool>()Copy the code

In the Transform, we subscribe to the requestCommond for the generated output

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 (" load "success) case. Let the error (error) : LXFProgressHUD.showError(error.localizedDescription) case .completed: output.refreshStatus.value = isReloadData ? .endHeaderRefresh : .endFooterRefresh } }).addDisposableTo(self.rx_disposeBag) }).addDisposableTo(rx_disposeBag)Copy the code

Initialize the refresh control in the ViewController

Set the refresh control for the tableView and use the requestCommond of Output to send signals in the callback to create the refresh control

tableView.mj_header = MJRefreshNormalHeader(refreshingBlock: { 
    vmOutput.requestCommond.onNext(true)
})
tableView.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: { 
    vmOutput.requestCommond.onNext(false)
})
Copy the code

Summary process:

  1. The requestCommond of the output sends a message telling the viewModel that we want to load the data

  2. The viewModel requests the data and changes the models after processing the JSON model or model array. The sections are signaled when the values of the models are changed. The sections are already bound to the items of the tableView in the ViewController. So the data in the tableView is going to be updated. We then change the refreshStatus property of output based on the request result

  3. When the value of refreshStatus property of output is changed, it will send a signal, because the external world has subscribed to refreshStatus of output before, it will process the state of the refresh control according to the new value of refreshStatus

Okay, RxSwiftDemo is attached. End and spend