Recently, I just finished my OC project. After careful consideration, I decided to use Swift in my next project. It happened that RxSwift had been out for some time, and the grammar was basically stable. So he came to try out RxSwift, and then he made a little Demo. Interested students could have a look at it.~
structure
.
├── Controller
│ └── LXFViewController.swift // Main View Controller
├── Extension
│ └── Response+ObjectMapper.swift // Response classification, Moya requests Json model or model array
├── Model
│ └── LXFModel.swift // Model
├── Protocol
│ └── LXFViewModelType.swift // Model protocol is defined.
├── Tool
│ ├── LXFNetworkTool.swift // Encapsulating Moya requests
│ └── LXFProgressHUD.swift // Packaged HUD
├── View
│ ├── LXFViewCell.swift // Custom cell
│ └── LXFViewCell.xib // xib file of cell
└── ViewModel
└── LXFViewModel.swift // View model
Third party Library
RxSwift // Necessary libraries for playing RxSwift
RxCocoa // Rx UIKit Foundation
NSObject+Rx // Provide us with rx_disposeBag
Moya/RxSwift // A network request library dedicated to RxSwift that encapsulates Alamofire
ObjectMapper // The Necessary Good Product of Json Transform Model
RxDataSources // Help US gracefully use the tableView data source approach
Then // Provide quick initialization of grammatical sugar
Kingfisher // Picture Loading Library
SnapKit // View Constraint Library
Reusable // Help US gracefully use custom cell s and view s without Optional
MJRefresh // Pull-Up Load, Pull-Down Refresh Library
SVProgressHUD // Easy to use HUD
Blackboard
Use of Moya
Moya is a network request Library Based on Alamofire. Here I use Moya/Swift, which adds interface support for RxSwift on the basis of Moya. Next, let's talk about the use of Moya.
1. Create an enumeration to store the request type. Here I set the corresponding path by the way, and then take it out uniformly and assign it directly.
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)
}
2. Write an extension for this enumeration and follow the TargetType protocol. Moya, a library protocol of this protocol, can press the Commond key + click the left key to enter the corresponding file for viewing.
extension LXFNetworkTool: TargetType {
/// baseURL Unify basic URL
var baseURL: URL {
return URL(string: "http://gank.io/api/data/")!
}
/// path Fields are appended to baseURL behind
var path: String {
switch self {
case .data(let type, let size, let index):
return "\(type.rawValue)/\(size)/\(index)"
}
}
/// HTTP The mode of request
var method: Moya.Method {
return .get
}
/// Request parameters(Coding will occur on request)
var parameters: [String: Any]? {
return nil
}
/// Parametric encoding(Use here URL Default mode)
var parameterEncoding: ParameterEncoding {
return URLEncoding.default
}
/// It's for unit testing, and it doesn't need to be written like I do.
var sampleData: Data {
return "LinXunFeng".data(using: .utf8)!
}
/// Tasks to be performed(Request: request Download: upload Upload: download)
var task: Task {
return .request
}
/// Whether to implement Alamofire Verify that the default value is false
var validate: Bool {
return false
}
}
3. Define a global variable for network requests for the entire project
let lxfNetTool = RxMoyaProvider<LXFNetworkTool>()
So far, we can use this global variable to request data.
RxDataSources
It's OK if you want to use the traditional way, but it loses the meaning of using RxSwift. Well, let's talk about how to gracefully implement tableView's data source. actually RxDataSources There are clear instructions on the official website, but let me summarize the whole process.
Concept point
RxDataSources are transmitted by section as data structure, which is very important. Maybe many students will be confused about this sentence. In this case, I have a numberOfSection in the traditional method of data source implementation. In many cases, we only need a section. So this method can be implemented or not. The default return is 1. This is for me. One of the puzzles we have is, [Table View is made up of row s]. I wonder if any of you who are sitting here think that way? If you have only one set, you have to go back to an array of sections!!!
Custom Section
Create a Section structure in our custom Model, and create an extension that follows the Section Model Type protocol to implement the corresponding protocol method. See the following way for a customary way of writing
LXFModel.swift
struct LXFSection {
// items are rows.
var items: [Item]
// You can also add what you need here, such as the title of headerView.
}
extension LXFSection: SectionModelType {
// Redefining the type of Item to LXFModel
typealias Item = LXFModel
// Ways to implement protocols
init(original: LXFSection, items: [LXFSection.Item]) {
self = original
self.items = items
}
}
2. Create a data source property under the controller
The following code is in the LXFViewController.swift file
// Create a data source property with a custom Selection type
let dataSource = RxTableViewSectionedReloadDataSource<LXFSection>()
Binding our cell with data source properties
// Binding cell
dataSource.configureCell = { ds, tv, ip, item in
// This place uses the Reusable library and complies with the corresponding protocol in LXFViewCell.
// Converting cells to non-optional cell types
let cell = tv.dequeueReusableCell(for: ip) as LXFViewCell
cell.picView.kf.setImage(with: URL(string: item.url))
cell.descLabel.text = "describe: \(item.desc)"
cell.sourceLabel.text = "source: \(item.source)"
return cell
}
3. Binding the sections sequence to our rows
output.sections.asDriver().drive(tableView.rx.items(dataSource:dataSource)).addDisposableTo(rx_disposeBag)
With great success, let's talk about the generation of section sequence.
Specification of ViewModel
We know that the idea of MVVM is to store the view display logic, validation logic, network request code originally in ViewController in ViewModel, so that our ViewController can be slimmed down. These logics are in the charge of ViewModel, the outside world does not need to care about, 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 LXFViewModel Type.
1. Create a LXFViewModelType.swift
LXFViewModelType.swift
// associatedtype Keyword is used to declare placeholders of a type as part of the protocol definition
protocol LXFViewModelType {
associatedtype Input
associatedtype Output
func transform(input: Input) -> Output
}
II. viewModel Compliance with LXFViewModelType Protocol
- We can define aliases for Input and Output of XFViewModel Type to distinguish. For example, if your viewModel is associated with the request homepage module, it can be named HomeInput and HomeOutput.
- We can enrich our Input and Output. You can see that I added a sequence for Output, a type for our custom LXFSection array, and a request type for Input (that is, what data to request, such as the data on the home page)
- We use the transform method to process the data carried by input and generate an Output.
Note: The following code has been partially deleted for ease of reading
LXFViewModel.swift
extension LXFViewModel: LXFViewModelType {
// An array of parsed models
let models = Variable<[LXFModel]>([])
// Define aliases for Input and Output of LXFViewModelType
typealias Input = LXFInput
typealias Output = LXFOutput
// Enriching our Input and Output
struct LXFInput {
// Network Request Type
let category: LXFNetworkTool.LXFNetworkCategory
init(category: LXFNetworkTool.LXFNetworkCategory) {
self.category = category
}
}
struct LXFOutput {
// sections data of tableView
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
// It is called when the values of models are changed, which is a feature of Variable.
return [LXFSection(items: models)] // Returns the section array
}.asDriver(onErrorJustReturn: [])
let output = LXFOutput(sections: sections)
// The next code is the network request, please view it in conjunction with the project, otherwise it will be inconvenient to read and understand.
}
}
Then we initialize our input in ViewController, get output through transform ation, and bind the sections sequence in our output to tableView items.
LXFViewController.swift
// Initialize input
let vmInput = LXFViewModel.LXFInput(category: .welfare)
// Get output through transform ation
let vmOutput = viewModel.transform(input: vmInput)
vmOutput.sections.asDriver().drive(tableView.rx.items(dataSource: dataSource)).addDisposableTo(rx_disposeBag)
Using MJRefresh in RxSwift
1. Define an enumeration LXFRefreshStatus to mark the current refresh status
enum LXFRefreshStatus {
case none
case beingHeaderRefresh
case endHeaderRefresh
case beingFooterRefresh
case endFooterRefresh
case noMoreData
}
2. Add a refreshStatus sequence to LXFOutput, of type LXFRefreshStatus
// Subscribe to the outside world and tell the outside world about the current refresh status of the tableView
let refreshStatus = Variable<LXFRefreshStatus>(.none)
After we make the network request and get the result, we modify the value of refreshStatus to the corresponding LXFRefreshStatus item.
3. refreshStatus of Outside Subscribed output
Outside subscribes to refreshStatus of output and performs corresponding operations based on the received values
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)
4. output provides a request Commond for requesting data
Publish Subject: It can be used as Observable or Observer, in other words, it can send or subscribe to signals.
// This property tells viewModel to load data (the value passed in is to indicate whether or not to reload)
let requestCommond = PublishSubject<Bool>()
In transform, we subscribe to the request Commond of 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 successful")
case let .error(error):
LXFProgressHUD.showError(error.localizedDescription)
case .completed:
output.refreshStatus.value = isReloadData ? .endHeaderRefresh : .endFooterRefresh
}
}).addDisposableTo(self.rx_disposeBag)
}).addDisposableTo(rx_disposeBag)
5. Initialize refresh control in ViewController
Set the refresh control for tableView and use output's request Commond to emit 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)
})
Summary process:
ViewController has got output, and when the data is loaded drop-down, it uses the request Commond of output to send information telling viewModel that we need to load the data.
viewModel requests data, modifies models after processing json to model or model array, and signals sections when the values of models are modified. Sections are bound to tableView items in ViewController, so tableView data is updated. Next, we modify the value of the refreshStatus attribute of output based on the result of the request.
When the value of the refreshStatus attribute of output changes, a signal is emitted. Since the refreshStatus of output has been subscribed to by the outside world, the refreshStatus status of the refreshStatus control is processed according to the new value of refreshStatus.
All right. Attached. RxSwiftDemo . End of the flower