Along the way, I feel that RxSwift is not as obscure and difficult as before. I even gradually like the idea of responsive programming. It takes objects as observable objects and subscribes to them. With the cooperation of filter operators, all logical operations are performed in background threads. This section is about the most powerful function of Rx.- Mapping, see the code: github
Some of the basic knowledge about mapping required in this section has been updated to github In the playground file in the code, students who have not been exposed to responsive programming should go ahead as before. playground In this section, we will gradually elaborate on the use of mapping operators in network requests through a case study.
This UI interface is very simple. It's a simple TableViewController. We mapped the JSON data to the Model and displayed it on Cell through network requests. https://api.github.com/repos/coderZsq/coderZsq.target.swift/events
For students who want to know how to build backstage and return simulated data, please click - >. Hybird Implements Hot Repair Architecture from MVC
{ "id": "5715074941", "type": "PushEvent", "actor": { "id": 19483268, "login": "coderZsq", "display_login": "coderZsq", "gravatar_id": "", "url": "https://api.github.com/users/coderZsq", "avatar_url": "https://avatars.githubusercontent.com/u/19483268?" }, "repo": { "id": 87806175, "name": "coderZsq/coderZsq.target.swift", "url": "https://api.github.com/repos/coderZsq/coderZsq.target.swift" }, "payload": { "push_id": 1688506435, "size": 1, "distinct_size": 1, "ref": "refs/heads/master", "head": "90306f546ee0e17d94891415a2f8938d28736dc7", "before": "778ac2b60b85a3e97e0a1e85e16cdb1061445181", "commits": [ { "sha": "90306f546ee0e17d94891415a2f8938d28736dc7", "author": { "email": "a13701777868@yahoo.com", "name": "Castie!" }, "message": "Update README.md", "distinct": true, "url": "https://api.github.com/repos/coderZsq/coderZsq.target.swift/commits/90306f546ee0e17d94891415a2f8938d28736dc7" } ] }, "public": true, "created_at": "2017-04-19T12:12:49Z" }
First, we look at the fields returned by the interface from the browser, and extract the fields we need to generate the Model for later mapping.
typealias AnyDict = [String: Any] class Event { let repo: String let name: String let imageUrl: URL let action: String init?(dictionary: AnyDict) { guard let repoDict = dictionary["repo"] as? AnyDict, let actor = dictionary["actor"] as? AnyDict, let repoName = repoDict["name"] as? String, let actorName = actor["display_login"] as? String, let actorUrlString = actor["avatar_url"] as? String, let actorUrl = URL(string: actorUrlString), let actionType = dictionary["type"] as? String else { return nil } repo = repoName name = actorName imageUrl = actorUrl action = actionType } var dictionary: AnyDict { return [ "repo" : ["name": repo], "actor": ["display_login": name, "avatar_url": imageUrl.absoluteString], "type" : action ] } }
We named the model Event and added a dictionary variable to return the serialized dictionary.
func fetchEvents(repo: String) { let response = Observable.from([repo]).map { urlString -> URL in return URL(string: "https://api.github.com/repos/\(urlString)/events")! }.map { [weak self] url -> URLRequest in var request = URLRequest(url: url) if let modifiedHeader = self?.lastModified.value { request.addValue(modifiedHeader as String,forHTTPHeaderField: "Last-Modified") } return request }.flatMap { request -> Observable<(HTTPURLResponse, Data)> in return URLSession.shared.rx.response(request: request) }.shareReplay(1) response.filter { response, _ in return 200..<300 ~= response.statusCode }.map { _, data -> [[String: Any]] in guard let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []), let result = jsonObject as? [[String: Any]] else { return [] } return result }.filter { objects in return objects.count > 0 }.map { objects in return objects.flatMap(Event.init) }.subscribe(onNext: { [weak self] newEvents in self?.processEvents(newEvents) }).addDisposableTo(bag) }
When everything is ready, let's talk about the most important part of this section. Let's explain it line by line.
- Observable.from() is used to create an observable object, which is passed into the repo field and map ped to a URL to return.
- Map the URL to a URL Request through map mapping, and write it to the request header if any modifications are made.
- Through the'flatmap'mapping, the URLRequest is mapped to HTTP URLResponse. Network requests are encapsulated in RxCocoa's URLSession.shared.rx.response(request:) method. Interested students can see the source code, of course, which is executed in the background thread.
- shareReplay(1) is used to share subscriptions. shareReplay can add previous response results to the buffer. This is why share() is not used and multiple requests are avoided.
For those who don't know the difference between flatmap and map, let's take a look at it. playground For example, look directly at the Array document in Swift.
/// Returns an array containing the non-`nil` results of calling the given /// transformation with each element of this sequence. /// /// Use this method to receive an array of nonoptional values when your /// transformation produces an optional value. /// /// In this example, note the difference in the result of using `map` and /// `flatMap` with a transformation that returns an optional `Int` value. /// /// let possibleNumbers = ["1", "2", "three", "///4///", "5"] /// /// let mapped: [Int?] = numbers.map { str in Int(str) } /// // [1, 2, nil, nil, 5] /// /// let flatMapped: [Int] = numbers.flatMap { str in Int(str) } /// // [1, 2, 5] /// /// - Parameter transform: A closure that accepts an element of this /// sequence as its argument and returns an optional value. /// - Returns: An array of the non-`nil` results of calling `transform` /// with each element of the sequence. /// /// - Complexity: O(*m* + *n*), where *m* is the length of this sequence /// and *n* is the length of the result. public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
When we get the response results, we filter the corresponding results. If you don't know about the filter, please refer to the previous article - >. Ling Wave Microstep of RxSwift Filter Operator
- By filtering out the wrong statusCode through filter, students who are not familiar with statusCode can consult the HTTP status code. The ~= operator refers to whether the value on the right side of the operator is within the scope on the left side of the operator and returns to the Bool type.
- After getting the correct data, the data is mapped into dictionary array type by map, using JSON serialization of the system.
- Filter again to filter out the data that is not returned.
- map the data structure into Event model again.
- Finally, the subscription execution is performed and added to DisposeBag.
Next, let's look at the execution of the [Event] model array after the mapping is completed.
func processEvents(_ newEvents: [Event]) { var updatedEvents = newEvents + events.value if updatedEvents.count > 50 { updatedEvents = Array<Event>(updatedEvents.prefix(upTo: 50)) } events.value = updatedEvents tableView.reloadData() refreshControl?.endRefreshing() let eventsArray = updatedEvents.map{ $0.dictionary } as NSArray eventsArray.write(to: eventsFileURL, atomically: true) }
- When you get the number of model arrays, if you get more than 50, take the first 50 data.
- Refresh the tableView, refreshControl ends the refresh, and caches the mapping model to the sandbox.
func cachedFileURL(_ fileName: String) -> URL { return FileManager.default.urls(for: .cachesDirectory, in: .allDomainsMask).first!.appendingPathComponent(fileName) } class ViewController: UITableViewController { fileprivate let repo = "coderZsq/coderZsq.target.swift" fileprivate let events = Variable<[Event]>([]) fileprivate let bag = DisposeBag() fileprivate let eventsFileURL = cachedFileURL("events.plist") fileprivate let modifiedFileURL = cachedFileURL("modified.txt") fileprivate let lastModified = Variable<NSString?>(nil) override func viewDidLoad() { super.viewDidLoad() title = repo let eventsArray = (NSArray(contentsOf: eventsFileURL) as? [[String: Any]]) ?? [] events.value = eventsArray.flatMap(Event.init) lastModified.value = try? NSString(contentsOf: modifiedFileURL, usedEncoding: nil) setupRefreshControl() } }
- cachedFileURL Cache Path Address
- Each time, the local mapping data is cached before the network request is executed.
The advantage of mapping and filtering is that everything is done in the background threads, and if mismatches occur in the process of mapping and filtering, subsequent operations will be interrupted and no longer executed.
Demonstration effect:
About: