Castie!

正态分布, 优劣伴生

北冥有鱼,其名为鲲(kūn)。鲲之大,不知其几千里也;化而为鸟,其名为鹏。鹏之背,不知其几千里也;怒而飞,其翼若垂天之云。是鸟也,海运则将徙于南冥。南冥者,天池也。


北海若曰:“井鼃不可以语于海者,拘于虚也;夏虫不可以语于冰者,笃于时也;曲士不可以语于道者,束于教也。今尔出于崖涘,观于大海,乃知尔丑,尔将可与语大理矣。

RxSwift 函数式映射运算符实操

一路走来, 感觉RxSwift也不像之前一样晦涩难懂了, 甚至渐渐的喜欢上了这种响应式编程的思想, 将对象作为可观察对象并进行订阅, 加上过滤操作符的协作, 一切的逻辑运算都在后台线程执行, 本节所要讲的是Rx中最为强大的功能 — 映射, 代码见:github

本节所需的一些关于映射的基本知识已经更新到github代码中的playground文件中, 没有接触过响应式编程的同学请和之前一样先行在playground中了解概要以便更好的理解本文. 本节我们就通过案例逐步精讲映射操作符在网络请求时的妙用.

这次的UI界面非常的简单, 就是一个简单的TableViewController, 我们通过网络请求将获取的JSON数据映射到Model上并展示在Cell上, API: https://api.github.com/repos/coderZsq/coderZsq.target.swift/events

想了解如何进行后台搭建并模拟数据返回的同学 请点击 –> Hybird 实现热修复架构 从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"
  }

我们先从浏览器看下接口所返回的字段, 并提取我们需要的字段生成模型Model, 以备之后的映射.

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
        ]
    }
}

我们将模型命名为Event, 添加了一个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)
    }

一切准备就绪, 我们就来说说本节最重要的部分了, 我们逐行来进行讲解.

  • 通过Observable.from()的方式创建一个可观察对象, 传入repo字段, 通过map映射成URL返回.
  • 通过map映射将URL映射成URLRequest, 如果有进行修改则写入请求头.
  • 通过’flatmap’映射将将URLRequest映射成HTTPURLResponse , 网络请求封装在RxCocoaURLSession.shared.rx.response(request:)方法中, 有兴趣的同学可以看下源码, 当然这些都是在后台线程中执行的.
  • 最后通过shareReplay(1)进行订阅共享. shareReplay可以将之前响应结果加入缓冲区. 这也是不使用share()的原因, 避免多次请求.

对于flatmap 和 map 之前的区别不了解的同学可以看下playground中的例子, 或者直接查看Swift中的Array文档.

    /// 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]

拿到了响应结果, 我们对相应结果进行过滤, 如果对过滤不了解的同学, 请翻看之前的文章 –> RxSwift 响应式编程实战 过滤运算符

  • 通过filter 过滤掉错误的statusCode, 对于statusCode不了解的同学可以查阅HTTP状态码的资料, ~=操作符是指操作符右边的值是否在操作符左边的范围之内, 返回Bool类型,
  • 得到正确的数据后, 通过map进行映射将数据映射成字典数组类型, 使用的是系统的JSON序列化.
  • 再次进行filter过滤, 过滤掉没有返回的数据.
  • 再次进行map映射, 将数据结构映射到Event模型中.
  • 最后进行订阅执行操作并添加到DisposeBag中.

接着我们来看下拿到了映射完成后的[Event]模型数组后的执行操作.

    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)
        
    }
  • 拿到模型数组的个数后取如果超过50个, 取前面50个数据.
  • 刷新tableView, refreshControl结束刷新, 并将映射模型缓存到沙盒.
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缓存路径地址
  • 每次先执行缓存本地的映射数据再进行网络请求.

映射和过滤的好处在于一切都在后台线程进行, 并在映射和过滤的过程中如果发生不匹配的情况, 之后的操作就会被打断, 不再执行.

演示效果:

About:

点击下方链接跳转!!

🌟 源码 请点这里🌟 »> 喜欢的朋友请点喜欢 »> 下载源码的同学请送下小星星 »> 有闲钱的壕们可以进行打赏 »> 小弟会尽快推出更好的文章和大家分享 »> 你的激励就是我的动力!!

最近的文章

RxSwift 函数式组合运算符实操

上周我们开启了RxSwift的学习之旅, 从可观察序列–>过滤运算符–>映射运算符, 接下来我们来说说组合运算符. 说实话, 对于之前的内容的学习, 我觉得还是比较通俗易懂的, 但是这次的组合运算符相比之前在理解难易程度上又上了个档次, 本节我们就来攻克这一挑战吧! 代码见:github本节所需的一些关于组合的基本知识已经更新到github代码中的playground文件中, 没有接触过响应式编程的同学请和之前一样先行在playground中了解概要以便更好的理解本文. 本...…

移动开发继续阅读
更早的文章

RxSwift 函数式过滤运算符实操

接着上一部分, 我们已经完成了函数式编程的简单实践, 也同时接触了Photos框架带来的自定义的便利, 主要熟悉了Variable, Subject 作为 Observable 进行订阅 subscribe 并通过处置包DisposeBag 处理的具体案例, 这一节我们来进一步完善相片拼图. 代码见:github本节所需的一些关于订阅信息过滤的基本知识已经更新到github代码中的playground文件中, 没有接触过响应式编程的同学请和之前一样先行在playground中了解概要以...…

移动开发继续阅读