开源项目分析(SwiftHub)Rxswift + MVVM + Moya 架构分析(一)第三方框架使用

开源项目分析(SwiftHub)Rxswift + MVVM + Moya 架构分析(一)第三方框架使用

1. SwiftHub项目简介

SwiftHub 是大神Khoren Markosyan 写的一个彻底采用Rxswift + MVVM + Moya 的架构的项目,代码很精简,想学习MVVM架构的认真去研究这个项目的设计,对你之后的编程思想和习惯都会有很大的帮助。(点击这里下载:SwiftHub源码react

SwiftHub项目简介

1.1 SwiftHub项目UI

UI页面1

SwiftHub UI2
SwiftHub UI3

SwiftHub UI4

1.2 SwiftHub项目代码结构

SwiftHub项目代码结构

2. SwiftHub项目编译,用到的第三方库简介

2.1 SwiftHub项目编译

下载源码后,进入SwiftHub-master主目录,先要下载安装第三方库,若是你cd SwiftHub-master/ 就直接执行pod install的话通常都会报错:ios

pod install的报错

分析报错缘由不难看出,已经提示咱们须要先pod repo update 一下更新你本地的cocos pod库。git

pod repo update一下

可能有的小伙伴网速不太好,pod install一直更新不了,这里提供了一份我编译好的源码:连接:pan.baidu.com/s/1qwkjY_Zr… 密码:60t7github

2.2 SwiftHub项目用到的第三方框架

  • 我只能惊叹,哇塞,怎么用了这么多第三方框架啊,我我的观点是不太主张用太多第三方框架,能本身实现都本身实现,除非要实现的功能必需要用第三方框架。由于第三方框架会大大增长咱们ipa包的大小,对于ipa大小有要求的是个灾难,例如以前咱们有一个项目使用Realm 做为DB框架,可是发现这个框架实在是太占内存了足足有将近90MB,而我只是想里面一个小小的数据库存储相关的代码,后面改为WCDB.swift框架,这个框架只有2MB左右。算法

  • 下面咱们先来看一下SwiftHub 项目用到的第三方框架吧: shell

    SwiftHub用到的第三方库

# Uncomment the next line to define a global platform for your project
platform :ios, '11.0'

use_frameworks!
inhibit_all_warnings!

target 'SwiftHub' do
    # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
    # Pods for SwiftHub

    # Networking
    pod 'Moya/RxSwift', '14.0.0-beta.2'  # https://github.com/Moya/Moya
    pod 'Apollo', '0.19.0'  # https://github.com/apollographql/apollo-ios

    # Rx Extensions
    pod 'RxDataSources', '~> 4.0'  # https://github.com/RxSwiftCommunity/RxDataSources
    pod 'RxSwiftExt', '~> 5.0'  # https://github.com/RxSwiftCommunity/RxSwiftExt
    pod 'NSObject+Rx', '~> 5.0'  # https://github.com/RxSwiftCommunity/NSObject-Rx
    pod 'RxViewController', '~> 1.0'  # https://github.com/devxoul/RxViewController
    pod 'RxGesture', '~> 3.0'  # https://github.com/RxSwiftCommunity/RxGesture
    pod 'RxOptional', '~> 4.0'  # https://github.com/RxSwiftCommunity/RxOptional
    pod 'RxTheme', '~> 4.0'  # https://github.com/RxSwiftCommunity/RxTheme
    #pod 'RxAnimated', '~> 0.4'  # https://github.com/RxSwiftCommunity/RxAnimated

    # JSON Mapping
    #pod 'ObjectMapper', :git => 'https://github.com/kajensen/ObjectMapper.git' # https://github.com/Hearst-DD/ObjectMapper
    pod 'Moya-ObjectMapper/RxSwift', :git => 'https://github.com/khoren93/Moya-ObjectMapper.git', :branch => 'moya14' # https://github.com/ivanbruel/Moya-ObjectMapper

    # Image
    pod 'Kingfisher', '~> 5.0'  # https://github.com/onevcat/Kingfisher

    # Date
    pod 'DateToolsSwift', '~> 4.0'  # https://github.com/MatthewYork/DateTools
    pod 'SwiftDate', '~> 6.0'  # https://github.com/malcommac/SwiftDate

    # Tools
    pod 'R.swift', '~> 5.0'  # https://github.com/mac-cain13/R.swift
    pod 'SwiftLint', '0.37.0'  # https://github.com/realm/SwiftLint

    # Keychain
    pod 'KeychainAccess', '~> 4.0'  # https://github.com/kishikawakatsumi/KeychainAccess

    # Fabric
    pod 'Fabric'
    pod 'Crashlytics'

    # UI
    pod 'NVActivityIndicatorView', '~> 4.0'  # https://github.com/ninjaprox/NVActivityIndicatorView
    pod 'ImageSlideshow/Kingfisher', '~> 1.8'  # https://github.com/zvonicek/ImageSlideshow
    pod 'DZNEmptyDataSet', '~> 1.0'  # https://github.com/dzenbot/DZNEmptyDataSet
    pod 'Hero', '~> 1.5.0'  # https://github.com/lkzhao/Hero
    pod 'Localize-Swift', '~> 3.0'  # https://github.com/marmelroy/Localize-Swift
    pod 'RAMAnimatedTabBarController', '~> 5.0'  # https://github.com/Ramotion/animated-tab-bar
    pod 'AcknowList', '~> 1.8'  # https://github.com/vtourraine/AcknowList
    pod 'KafkaRefresh', '~> 1.0'  # https://github.com/OpenFeyn/KafkaRefresh
    pod 'WhatsNewKit', '~> 1.0'  # https://github.com/SvenTiigi/WhatsNewKit
    pod 'Highlightr', '~> 2.0'  # https://github.com/raspu/Highlightr
    pod 'DropDown', '~> 2.0'  # https://github.com/AssistoLab/DropDown
    pod 'Toast-Swift', '~> 5.0'  # https://github.com/scalessec/Toast-Swift
    pod 'HMSegmentedControl', '~> 1.0'  # https://github.com/HeshamMegid/HMSegmentedControl
    pod 'FloatingPanel', '~> 1.0'  # https://github.com/SCENEE/FloatingPanel
    pod 'MessageKit', '~> 3.0'  # https://github.com/MessageKit/MessageKit
    pod 'MultiProgressView', '~> 1.0'  # https://github.com/mac-gallagher/MultiProgressView

    # Keyboard
    pod 'IQKeyboardManagerSwift', '~> 6.0'  # https://github.com/hackiftekhar/IQKeyboardManager

    # Auto Layout
    pod 'SnapKit', '~> 5.0'  # https://github.com/SnapKit/SnapKit

    # Code Quality
    pod 'FLEX', :git => 'https://github.com/khoren93/FLEX.git', :branch => 'remove_private_api' # https://github.com/Flipboard/FLEX
    pod 'SwifterSwift', '~> 5.0'  # https://github.com/SwifterSwift/SwifterSwift
    pod 'BonMot', '~> 5.0'  # https://github.com/Rightpoint/BonMot

    # Logging
    pod 'CocoaLumberjack/Swift', '~> 3.0'  # https://github.com/CocoaLumberjack/CocoaLumberjack

    # Analytics
    # https://github.com/devxoul/Umbrella
    pod 'Umbrella/Mixpanel', '~> 0.8'
    pod 'Umbrella/Firebase'
    pod 'Mixpanel', '~> 3.0'  # https://github.com/mixpanel/mixpanel-iphone
    pod 'Firebase/Analytics'

    # Ads
    pod 'Firebase/AdMob'
    pod 'Google-Mobile-Ads-SDK', '7.52.0'
    
    target 'SwiftHubTests' do
        inherit! :search_paths
        # Pods for testing
        pod 'Quick', '~> 2.0'  # https://github.com/Quick/Quick
        pod 'Nimble', '~> 8.0'  # https://github.com/Quick/Nimble
        #pod 'RxNimble', '~> 4.0'  # https://github.com/RxSwiftCommunity/RxNimble
        pod 'RxAtomic', :modular_headers => true
        pod 'RxBlocking'  # https://github.com/ReactiveX/RxSwift
        pod 'Firebase'
    end
end

target 'SwiftHubUITests' do
    inherit! :search_paths
    # Pods for testing
end


post_install do |installer|
    # Cocoapods optimization, always clean project after pod updating
    Dir.glob(installer.sandbox.target_support_files_root + "Pods-*/*.sh").each do |script|
        flag_name = File.basename(script, ".sh") + "-Installation-Flag"
        folder = "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
        file = File.join(folder, flag_name)
        content = File.read(script)
        content.gsub!(/set -e/, "set -e\nKG_FILE=\"#{file}\"\nif [ -f \"$KG_FILE\" ]; then exit 0; fi\nmkdir -p \"#{folder}\"\ntouch \"$KG_FILE\"")
        File.write(script, content)
    end
    
    # Enable tracing resources
    installer.pods_project.targets.each do |target|
      if target.name == 'RxSwift'
        target.build_configurations.each do |config|
          if config.name == 'Debug'
            config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['-D', 'TRACE_RESOURCES']
          end
        end
      end
    end
end

复制代码
  • 接下来,咱们来分析这些第三方库都是用来干什么,说不定哪天你的项目也能够用到呢。

2.2.1 网络库

用到的网络库

2.2.1.1 Alamofire

AlamofireAFNetwork 是一对兄弟,是出自同一个公司的产品, 它是一个很好的Swift编写的网络框架库,提供了HTTP相关接口,能轻松实现链式请求和响应,能实现文件上传,下载,断点续传,后台下载等功能。数据库

安装方式:编程

  • CocoaPods安装:pod 'Alamofire', '~> 5.1'
  • Carthage 安装:github "Alamofire/Alamofire" ~> 5.1

安装环境要求:json

iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+ Xcode 11+ Swift 5.1+swift

提供的功能特性:

  1. 可链请求/响应方法
  2. URL / JSON参数编码
  3. 上传文件/数据/流/ MultipartFormData
  4. 使用请求或简历数据下载文件
  5. 身份验证与URLCredential
  6. HTTP响应验证
  7. 上传和下载进度闭包与进度
  8. cURL命令输出
  9. 动态调整和重试请求
  10. TLS证书和公钥固定
  11. 网络可达性
  12. 全面的单元和集成测试覆盖
  13. 完整的文档

为了让 Alamofire更专一于处理网络相关的事情,Alamofire软件基金会已经建立了额外的组件库来为Alamofire生态系统带来额外的功能。如:AlamofireImage库和 AlamofireNetworkActivityIndicator

  1. AlamofireImage: 一个图像库,包括图像响应序列化器,UIImage和UIImageView扩展,自定义图像过滤器,一个自动清除内存缓存和一个基于优先级的图像下载系统。
  2. AlamofireNetworkActivityIndicator : 使用Alamofire控制iOS上的网络活动指示器的可见性。它包含可配置的延迟计时器,以帮助减小闪烁,并支持不受Alamofire管理的URLSession实例。
  • Alamofire框架结构图:

    Alamofire框架结构图

  • 关于Alamofire的使用能够参考个人一些博客:

Alamofire学习(一)网络基础

Alamofire(二)URLSession

Alamofire(三)后台下载原理

  • 网络请求步骤:
  1. 设置请求url
  2. 设置URLRequest对象,配置请求相关信息
  3. 建立会话配置URLSessionConfiguration
  4. 建立会话URLSession
  5. 建立任务和设置请求回调,并发起请求

简单请求代码:

func responseData() {
    let url = "http://onapp.kongyulu.top/public/?s=api/test/list"
    Alamofire.request(url).responseJSON {
        (response) in
        switch response.result{
        case .success(let json):
            print("json:\(json)")
            let dict = json as! Dictionary<String, Any>
            let list = dict["data"] as! Array<AnyObject>
            guard let result = [UserModel1].deserialize(from: list) else{return}
            self.observable.onNext(result as [Any])
            break
        case .failure(let error):
            print("error:\(error)")
            break
        }
    }
}
复制代码

其中URLSessionConfiguration提供了框架的相关配置:

主要提供了如下3中方式:

  1. default:默认模式,经常使用模式,在该模式下系统会建立持久化缓存,并在用户的钥匙串中保存证书
  2. ephemeral:不支持持久性存储,全部内容的会随着session的生命周期结束而释放 background:与default模式相似,在该模式下会建立一个独立线程来传输网络请求数据,能够在后台乃至APP关闭的时候也能够进行数据传输
  • 建立会话:
let configuration = URLSessionConfiguration.background(withIdentifier: "request_id")
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
session.dataTask(with: request) { (data, response, error) in
    do {
        let list =  try JSONSerialization.jsonObject(with: data!, options: .allowFragments)
        print(list)
    }catch{
        print(error)
    }
}.resume()
复制代码

此外还提供了不少属性来按需配置

  • 常规属性:
  1. identifier:配置对象的后台会话标识符
  2. httpAdditionalHeaders:与请求一块儿发送的附加头文件字典
  3. networkServiceType:网络服务的类型
  4. allowsCellularAccess:一个布尔值,用因而否应经过蜂窝网络进行链接
  5. timeoutIntervalForRequest:等待附加数据的超时时间
  6. timeoutIntervalForResource:资源请求容许的最大时间范围
  7. sharedContainerIdentifier:应将后台URL会话中的文件下载到的共享容器的标识符
  8. waitsForConnectivity:一个布尔值,指示会话是否应等待链接变为可用仍是当即失败
  • 设置Cookie策略:
  1. httpCookieAcceptPolicy:决定什么时候接受cookie的策略常量
  2. httpShouldSetCookies:一个布尔值,肯定请求是否包含来自cookie存储区的cookie
  3. httpCookieStorage:用于会话中存储cookie的cookie存储区
  4. HTTPCookie:该对象为不可变对象,从包含cookie属性的字典初始化,支持两个不一样的cookie版本,v0、v1
  • 设置安全策略:
  1. TLS协议:用于在两个通讯应用程序之间提供保密性和数据完整性
  2. tlsMaximumSupportedProtocol:在此会话中创建链接时客户端应请求的最大TLS协议版本
  3. tlsMinimumSupportedProtocol:协议协商期间应接受的最小TLS协议
  4. urlCredentialStorage:为身份验证提供凭据的凭据存储区
  • 设置缓存策略:
  1. urlCache:用于为会话中的请求提供缓存响应的URL缓存
  2. requestCachePolicy:决定什么时候从缓存中返回响应的预约义常量
  • 支持后台模式:
  1. sessionSendsLaunchEvents:一个布尔值,指示当传输完成时,应用程序应在后台恢复仍是启动
  2. isDiscretionary:一个布尔值,用于肯定后台任务是否能够由系统自行安排已得到最佳性能
  3. shouldUseExtendedBackgroundIdleMode:一个布尔值,指示当应用程序转移到后台时是否应保持TCP链接打开
  • 支持自定义协议
  1. protocolClasses:在会话中处理请求的额外协议子类的数组
  2. URLProtocol:该对象用来处理加载协议特定URL数据
  • 支持多路径TCP:
  1. multipathServiceType:指定用于经过Wi-Fi和蜂窝接口传输数据的多路径TCP链接策略的服务类型
  • 设置HTTP策略和代理属性:
  1. httpMaximumConnectionsPerHost:同时链接到给定主机的最大数量
  2. httpShouldUsePipelining:一个布尔值,用于肯定会话是否使用HTTP流水线
  3. connectionProxyDictionary:包含相关要在此会话中使用的代理信息的字典
  • 支持链接更改:
  1. waitsForConnectivity:一个布尔值,指示会话应等待链接可用仍是当即失败
2.2.1.2 Rxswift

Rxswift家族提供很是好的函数响应式编程框架,使用Rxswift编写代码可让代码变得很是简洁,逻辑清晰,若是配合Moya + Rxswift + MVVM架构,真的是很完美,这个开源项目SwiftHub就是这样的一个完美的项目。

ReactiveX(简写:Rx)是一个能够帮助咱们简化异步编程的框架。而 RxSwift 是 Rx 的 Swift 版本。除了 RxSwift,还有 RxJava、RxJS、Rx.Net 等,对应的OC 版本则是 RAC(ReactiveCocoa),这里是 RxSwift 的 github 地址 ,已经有了将近 18.2K 颗星了。

Rxswift简洁图

Rxswift构成图

RxSwift: RxSwift的核心,提供由ReactiveX(主要)定义的Rx标准。它没有其余依赖项。 RxCocoa: 为通常的iOS/macOS/watchOS & tvOS应用程序开发提供特定于cocoa的功能,如绑定、特性等。它同时依赖于RxSwift和RxRelay。 RxRelay: 提供发布中继和行为中继,这两个简单的主题包装器。这取决于RxSwift。 RxTest and RxBlocking: 为基于rx的系统提供测试功能。这取决于RxSwift。

  • 关于Rxswift的使用能够参考个人一些博客:

Rxswift(一)函数响应式编程思想

RxSwift (二)序列核心逻辑分析

RxSwift (三)Observable的建立,订阅,销毁

RxSwift(四)高阶函数

RxSwift(五)(Rxswift对比swift,oc用法)

Rxswift (六)销毁者Dispose源码分析

RxSwift(七)Rxswift对比swift用法

RxSwift (十) 基础使用篇 1- 序列,订阅,销毁

RxSwift学习之十二 (基础使用篇 3- UI控件扩展)

Rxswift一些简单使用以下:

  • button点击事件:
//MARK: - RxSwift应用-button响应
func setupButton() {
    // 传统UI事件
    self.button.addTarget(self, action: #selector(didClickButton), for: .touchUpInside)

    // 这样的操做 - 不行啊!代码逻辑与事件逻辑分层
    self.button.rx.tap
        .subscribe(onNext: { [weak self] in
            print("点了,小鸡炖蘑菇")
            self?.view.backgroundColor = UIColor.orange   
        })
        .disposed(by: disposeBag) 
}
复制代码
  • textfiled文本响应
//MARK: - RxSwift应用-textfiled
func setupTextFiled() {
    // 咱们若是要对输入的文本进行操做 - 好比输入的的内容 而后咱们获取里面的偶数
    // self.textFiled.delegate = self
    // 感受是否是特别恶心
    // 下面咱们来看看Rx
    self.textFiled.rx.text.orEmpty.changed.subscribe(onNext: { (text) in
        print("监听到了 - \(text)")
    }).disposed(by: disposeBag)

    self.textFiled.rx.text.bind(to: self.button.rx.title()).disposed(by: disposeBag)
}
复制代码
  • scrollView使用
//MARK: - RxSwift应用-scrollView
func setupScrollerView() {
    scrollView.rx.contentOffset.subscribe(onNext: { [weak self] (content) in
        self?.view.backgroundColor = UIColor.init(red: content.y/255.0*0.8, green: content.y/255.0*0.3, blue: content.y/255.0*0.6, alpha: 1);
        print(content.y)
    }).disposed(by: disposeBag)
}
复制代码
  • KVO
//MARK: - RxSwift应用-KVO
func setupKVO() {
    // 系统KVO 仍是比较麻烦的
    // person.addObserver(self, forKeyPath: "name", options: .new, context: nil)
    person.rx.observeWeakly(String.self, "name").subscribe(onNext: { (change) in
        print(change ?? "helloword")
    }).disposed(by: disposeBag)
}
复制代码
  • 通知
//MARK: - 通知
func setupNotification(){
    NotificationCenter.default.rx
        .notification(UIResponder.keyboardWillShowNotification)
        .subscribe { (event) in
            print(event)
    }.disposed(by: disposeBag)
}
复制代码
  • 手势
//MARK: - 手势
func setupGestureRecognizer(){
    let tap = UITapGestureRecognizer()
    self.label.addGestureRecognizer(tap)
    self.label.isUserInteractionEnabled = true
    tap.rx.event.subscribe { (event) in
        print("点了label")
    }.disposed(by: disposeBag)  
}
复制代码
  • 网络请求
//MARK: - RxSwift应用-网络请求
func setupNextwork() {
    let url = URL(string: "https://www.baidu.com")
    URLSession.shared.rx.response(request: URLRequest(url: url!))
        .subscribe(onNext: { (response, data) in
            print("response ==== \(response)")
            print("data ===== \(data)")
        }, onError: { (error) in
            print("error ===== \(error)")
        }).disposed(by: disposeBag)
}
复制代码
  • 定时器
//MARK: - RxSwift应用-timer定时器
func setupTimer() {
    timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
    timer.subscribe(onNext: { (num) in
        print("hello word \(num)")
    }).disposed(by: disposeBag)
}
复制代码
2.2.1.3 Moya
  • 源码下载:Moya

Moya是一个网络抽象层,它在底层将Alamofire进行封装,对外提供更简洁的接口供开发者调用。在Objective-C中,大部分开发者会使用AFNetwork进行网络请求,当业务复杂一些时,会对AFNetwork进行二次封装,编写一个适用于本身项目的网络抽象层。在Objective-C中,有著名的YTKNetwork,它将AFNetworking封装成抽象父类,而后根据每一种不一样的网络请求,都编写不一样的子类,子类继承父类,来实现请求业务。Moya在项目层次中的地位,有点相似于YTKNetwork。能够看下图对比

Moya 扮演的角色

若是单纯把Moya等同于swift版的YTKNetwork,那就是比较错误的想法了。Moya的设计思路和YTKNetwork差距很是大。上面我在介绍YTKNetwork时在强调子类和父类,继承,是由于YTKNetwork是比较经典的利用OOP思想(面向对象)设计的产物。基于swift的Moya虽然也有使用到继承,可是它的总体上是以POP思想(Protocol Oriented Programming,面向协议编程)为主导的。

  • Moya的模块组成:

Moya的模块组成

  1. Providerprovider是一个提供网络请求服务的提供者。经过一些初始化配置以后,在外部能够直接用provider来发起request。
  2. Request:在使用Moya进行网络请求时,第一步须要进行配置,来生成一个Request。首先按照官方文档,建立一个枚举,遵照TargetType协议,并实现协议所规定的属性。为何要建立枚举来遵照协议,枚举结合switch语句,使得API管理起来比较方便。
  3. 根据建立了一个遵照TargetType协议的名为Myservice的枚举,咱们完成了以下几个变量的设置。
baseURL
	path
	method
	sampleData
	task
	headers
复制代码
  • Moya使用
import UIKit
import Moya
import RxCocoa
import Result
import SwiftyJSON

//初始rovider
let KApiProvider = MoyaProvider<KNetworkAPI>(plugins: [RequestLoadingPlugin()])

let K_Search_Base = "http://www.baid.com/search"

/** 请求的endpoints)**/
//请求分类
enum KNetworkAPI {
    case shareNavList:
    case shareList(pageSize: Int, pageNum: Int):
}
//请求配置
extension KNetworkAPI: TargetType {
    //服务器地址
    public var baseURL: URL {
        switch self {
        default:
            return URL(string: K_Search_Base)!
        }
    }
    
    //各个请求的具体路径
    public var path: String {
        switch self {
        case .shareNavList:
            return "manage/navigation/getNavigationList"
        default:
            return "default/list"
        }
    }
    
    //请求类型
    public var method: Moya.Method {
        switch self {
       
        default:
            return .get
        }
    }
    
    //请求任务事件(这里附带上参数)
    public var task: Task {
        switch self {
        case .shareNavList:
            return .requestPlain
       case .shareList(let pageSize, let pageNum):
            var params: [String: Any] = [:]
            params["pageSize"] = pageSize
            params["pageNum"] = pageNum
            return .requestParameters(parameters: params, encoding: URLEncoding.default)
        }
    }
    //是否执行Alamofire验证
    public var validate: Bool {
        return false
    }
    //这个就是作单元测试模拟的数据,
// 只会在单元测试文件中有做用
    public var sampleData: Data {
        return "{}".data(using: String.Encoding.utf8)!
    }

    //请求头
    public var headers: [String: String]? {
        switch self {
        default:
            return ["Content-type": "application/json"]
        }
    }
}


RequestLoadingPlugin 插件用来显示UI相关,捕获网络异常等操做,给出提示,代码以下:
```swift


import UIKit
import Foundation
import MBProgressHUD
import Moya
import Result

class RequestLoadingPlugin: PluginType {
    
    func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
        print("prepare")
        var mRequest = request
        mRequest.timeoutInterval = 20
        return mRequest
    }
    func willSend(_ request: RequestType, target: TargetType) {
        print("开始请求")
        if SwiftIsShowHud == true {
            let keyViewController = UIApplication.shared.keyWindow?.rootViewController
            if (keyViewController != nil) {
                MBProgressHUD.showAdded(to: keyViewController!.view, animated: true)
            }
        }
        
    }
    
    func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
        print("结束请求")
        let keyViewController = UIApplication.shared.keyWindow?.rootViewController
        if (keyViewController != nil) {
            MBProgressHUD.hide(for: keyViewController!.view, animated: true)
            // MBProgressHUD.
        }
        
        
       guard case Result.failure(_) = result
        else {
            let respons = result.value
            let dic: Dictionary<String, Any>? =
                try? JSONSerialization.jsonObject(with: respons!.data, options: .mutableContainers) as! Dictionary<String, Any>
            
            if dic != nil {
                if dic?.keys.contains("status") == true {
                    if dic?["status"] as! Int == 11 || dic?["status"] as! Int == 12 {
                        print("Token 失效")
                    }
                }
                
                if dic?.keys.contains("code") == true {
                    if dic?["code"] as! Int == 11 || dic?["code"] as! Int == 12 {
                        print("Token 失效")
                    }
                }
            }
            return
        }
        let errorReason: String = (result.error?.errorDescription)!
        print("请求失败:\(errorReason)")
        var tip = ""
        if errorReason.contains("The Internet connection appears to be offline") {
            tip = "网络不给力,请检查您的网络"
        }else if errorReason.contains("Could not connect to the server") {
            tip = "没法链接服务器"
        }else {
           tip = "请求失败"
        }
        /// 使用tip文字 进行提示
    }
}


复制代码
  • 调用代码以下:
import RxSwift
import RxCocoa
import ObjectMapper

KApiProvider.rx.request(input.category)
                .mapObject(KBaseModel<T>.self)
                .subscribe(onSuccess: { (baseModel) in
                    print("请求成功 返回数据以下")
                    if baseModel.status != 0 {
                        return
                    }
                }, onError: {error in
                    print("Error:请求错误")
                }).disposed(by: self.disposeBag)
            }, onError: { (error) in
                
        }, onCompleted: {
            
        }) {
            }.disposed(by: disposeBag)


复制代码

2.2.2 数据解析库

数据解析库

2.2.2.1 ObjectMapper

ObjectMapper 是一个使用 Swift 语言编写的数据模型转换框架,咱们能够方便的将模型对象转换为JSON,或者JSON生成相应的模型类。

有以下特色:

  1. 将JSON映射到对象
  2. 将对象映射到JSON
  3. 支持嵌套对象(在数组或字典中单独使用)
  4. 支持映射过程当中自定义转换
  5. 支持结构体
  6. 支持Immutable
  • 模型类定义:

建立模型类须要实现Mappable接口,包括init?(map: Map)func mapping(map: Map)两个方法 ObjectMapper使用<-特殊运算符表示JSON与模型属性之间的映射关系

实例代码以下:

class User: Mappable {
    var username: String?
    var age: Int?
    var weight: Double!
    var array: [Any]?
    var dictionary: [String : Any] = [:]
    var bestFriend: User?                       // Nested User object
    var friends: [User]?                        // Array of Users
    var birthday: Date?

//对象序列号以前验证JSON合法性,不符合条件返回nil阻止映射发生
    required init?(map: Map) {
      // 检查JSON是否有name字段
      if map.JSON["name"] == nil {
        return nil
      }
    }

    // Mappable
    func mapping(map: Map) {
        username    <- map["username"]
        age         <- map["age"]
        weight      <- map["weight"]
        array       <- map["arr"]
        dictionary  <- map["dict"]
        bestFriend  <- map["best_friend"]
        friends     <- map["friends"]
        birthday    <- (map["birthday"], DateTransform())
    }
}

struct Temperature: Mappable {
    var celsius: Double?
    var fahrenheit: Double?
    init?(map: Map) {
    }
    mutating func mapping(map: Map) {
        celsius     <- map["celsius"]
        fahrenheit  <- map["fahrenheit"]
    }
}
复制代码
  • JSON字符串转模型类:
let user = User(JSONString: JSONString)
//使用Mapper
let user = Mapper<User>().map(JSONString: JSONString)
//使用Mapper转模型数组
let users: [User] = Mapper<User>().mapArray(JSONString: JSONString)
复制代码
  • 模型类转JSON字符串:
//prettyPrint参数是为了打印可读性json
let JSONString = user.toJSONString(prettyPrint: true)
//使用Mapper
let JSONString = Mapper().toJSONString(users, prettyPrint: true)
复制代码
  • 支持的类型:
Int
Bool
Double
Float
String
RawRepresentable (Enums)
Array<Any>
Dictionary<String, Any>
Object<T: Mappable>
Array<T: Mappable>
Array<Array<T: Mappable>>
Set<T: Mappable>
Dictionary<String, T: Mappable>
Dictionary<String, Array<T: Mappable>>
Optionals of all the above //上述的可选类型
Implicitly Unwrapped Optionals of the above //上述的隐式解析可选类型

复制代码
  • 嵌套对象的简单映射:
import ObjectMapper

class UserInfo: Mappable {
    var username: String?
    var age: Int?
    var weight: Double!
    var dictionary: UserInfo?
    var value: String?


    required init?(map: Map) {
    }
    
    func mapping(map: Map) {
        username    <- map["username"]
        age         <- map["age"]
        weight      <- map["weight"]
        dictionary  <- map["dictionary"]
        value  <- map["dictionary.username"]
    }
}
复制代码
  • 自定义转换: ObjectMapper提供了一些类型转换如DateTransformDataTransformHexColorTransform,可是没有提供的就须要咱们自定义,下面举例实现NSURLTransform
import UIKit
import ObjectMapper

class NSURLTransform: TransformType {
    typealias Object = NSURL
    typealias JSON = String
    
    func transformFromJSON(_ value: Any?) -> NSURL? {
        guard let string = value as? String else{
            return nil
        }
        return NSURL.init(string: string)
    }
    
    func transformToJSON(_ value: NSURL?) -> String? {
        guard let url = value else{
            return nil
        }
        return url.absoluteString
    }

}
复制代码

此外,还有一个比较好用的框架AlamofireObjectMapper

该框架能够结合 AlamofireObjectMapper 使用, 为Alamofire的Request类扩展出了responseObjectresponseArray 方法, 更方便的将网络通讯返回的JSON数据转换成对象

AlamofireObjectMapper 简介
下面是它的样列代码:

let URL = "..."
Alamofire.request(.GET, URL).responseObject { (response: DataResponse<WeatherResponse>) in

    let weatherResponse = response.result.value

    if let threeDayForecast = weatherResponse?.threeDayForecast {
        for forecast in threeDayForecast {
            print(forecast.day)
            print(forecast.temperature)           
        }
    }
}
复制代码
2.2.2.2 Moya-ObjectMapper/Swift

Moya-ObjectMapper/Swift简介

安装方式:

pod 'Moya-ObjectMapper'
#The subspec if you want to use the bindings over RxSwift.

pod 'Moya-ObjectMapper/RxSwift'
#The subspec if you want to use the bindings over ReactiveSwift.

pod 'Moya-ObjectMapper/ReactiveSwift'
复制代码
  • 使用: 先建立一个模型:
import Foundation
import ObjectMapper

// MARK: Initializer and Properties
struct Repository: Mappable {

  var identifier: Int!
  var language: String?
  var url: String!

  // MARK: JSON
  init?(map: Map) { }

  mutating func mapping(map: Map) {
    identifier <- map["id"]
    language <- map["language"]
    url <- map["url"]
  }
}
复制代码

没有RxswiftReactiveSwift 的使用方法:

GitHubProvider.request(.userRepositories(username), completion: { result in

    var success = true
    var message = "Unable to fetch from GitHub"

    switch result {
    case let .success(response):
        do {
            if let repos = try response.mapArray(Repository) {
              self.repos = repos
            } else {
              success = false
            }
        } catch {
            success = false
        }
        self.tableView.reloadData()
    case let .failure(error):
        guard let error = error as? CustomStringConvertible else {
            break
        }
        message = error.description
        success = false
    }
})
复制代码

Rxswift的使用方式:

GitHubProvider.request(.userRepositories(username))
  .mapArray(Repository.self)
  .subscribe { event -> Void in
    switch event {
    case .next(let repos):
      self.repos = repos
    case .error(let error):
      print(error)
    default: break
    }
  }.addDisposableTo(disposeBag)
复制代码

ReactiveSwift的使用方式:

GitHubProvider.request(.userRepositories(username))
  .mapArray(Repository.self)
  .start { event in
    switch event {
    case .value(let repos):
      self.repos = repos
    case .failed(let error):
      print(error)
    default: break
    }
  }
复制代码

ReactiveSwift简介:

ReactiveSwift简介

ReactiveSwift提供了可组合的、声明性的和灵活的原语,这些原语是围绕着随时间流逝的价值流的宏大概念构建的。 这些原语能够用来统一地表示常见的Cocoa和泛型编程模式,它们本质上是一种观察行为,例如委托模式、回调闭包、通知、控制操做、响应链事件和键值观察(KVO)。 由于全部这些不一样的机制均可以用相同的方式表示,因此很容易以声明的方式将它们组合在一块儿,用更少的意大利面条代码和状态来弥补差距。

2.2.3 Rxswift 框架和相关扩展

2.2.3.1 RxDataSources

RxDataSources简介

  1. O(N)计算差别的算法: 该算法假设全部的部分和项都是惟一的,所以没有歧义。 若是有歧义,回退自动对非动画刷新。
  2. 它应用额外的启发式方法,向分段视图发送最少数量的命令: 尽管运行时间是线性的,但发送命令的首选数量一般比线性少得多 最好(也可能)将更改的数量限制在较小的范围内,若是更改的数量增加为线性,则只需进行正常的从新加载
  3. 支持扩展项目和节结构: 用IdentifiableType和Equatable扩展你的项目,用AnimatableSectionModelType扩展你的部分
  4. 支持两个层次动画的全部组合的节和项目: 节动画:插入,删除,移动 项目动画:插入、删除、移动、重载(若是旧值不等于新值)
  5. 可配置的动画类型插入,重载和删除(自动,淡出,…)
  6. 示例应用程序
  7. 随机压力测试(示例app)
  8. 支持开箱即用的编辑(示例应用程序)
  9. 适用于UITableView和UICollectionView

安装:

CocoaPods

Podfile

pod 'RxDataSources', '~> 4.0'
复制代码

Carthage

Cartfile

github "RxSwiftCommunity/RxDataSources" ~> 4.0
复制代码
  • 使用:
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, Int>>(configureCell: configureCell)
Observable.just([SectionModel(model: "title", items: [1, 2, 3])])
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)
复制代码
2.2.3.2 RxSwiftExt

若是您正在使用Rxswift,您可能会遇到内置操做符不能提供所需功能的状况。为了不膨胀,Rxswift内核被设计得尽量紧凑。这个存储库的目的是提供额外的方便操做符和反应性扩展。

安装: RxSwiftExt的这个分支以Swift 5为目标。x和Rxswift 5.0.0或更高版本。

若是您正在寻找RxSwiftExt的Swift 4版本,请使用该框架的3.4.0版本。

CocoaPods

Add to your Podfile:

pod 'RxSwiftExt', '~> 5'
复制代码

这将同时安装RxSwift和RxCocoa扩展。若是您只想安装RxSwift扩展,而不想安装RxCocoa扩展,只需使用:

pod 'RxSwiftExt/Core'
复制代码

Using Swift 4:

pod 'RxSwiftExt', '~> 3'
复制代码

Carthage

github "RxSwiftCommunity/RxSwiftExt"
复制代码

RxSwiftExt扩展了以下操做:

  • unwrap: 打开选项并过滤掉空值。
Observable.of(1,2,nil,Int?(4))
    .unwrap()
    .subscribe { print($0) }
复制代码

结果:

next(1)
next(2)
next(4)
复制代码
  • ignore:忽略特定元素。
Observable.from(["One","Two","Three"])
    .ignore("Two")
    .subscribe { print($0) }
复制代码

结果:

next(One)
next(Three)
completed

复制代码
  • ignoreWhen:根据闭包忽略元素。
Observable<Int>
    .of(1,2,3,4,5,6)
    .ignoreWhen { $0 > 2 && $0 < 6 }
    .subscribe { print($0) }
复制代码

结果:

next(1)
next(2)
next(6)
completed
复制代码
  • once:将下一个元素精确地发送一次到接收它的第一个订阅服务器。进一步的订阅者将获得一个空序列。
let obs = Observable.once("Hello world")
  print("First")
  obs.subscribe { print($0) }
  print("Second")
  obs.subscribe { print($0) }
复制代码

结果:

First
next(Hello world)
completed
Second
completed
复制代码
  • distinct:只有在序列中从未出现过元素时,才将它们传递过去。
Observable.of("a","b","a","c","b","a","d")
    .distinct()
    .subscribe { print($0) }
复制代码

结果:

next(a)
next(b)
next(c)
next(d)
completed
复制代码
  • mapTo:用提供的值替换每一个元素。
Observable.of(1,2,3)
    .mapTo("Nope.")
    .subscribe { print($0) }
复制代码

结果:

next(Nope.)
next(Nope.)
next(Nope.)
completed
复制代码
  • mapAt:将每一个元素转换为提供的键路径上的值。
struct Person {
    let name: String
}

Observable
    .of(
        Person(name: "Bart"),
        Person(name: "Lisa"),
        Person(name: "Maggie")
    )
    .mapAt(\.name)
    .subscribe { print($0) }
复制代码

结果:

next(Bart)
next(Lisa)
next(Maggie)
completed
复制代码
  • not:否认的布尔值。
Observable.just(false)
    .not()
    .subscribe { print($0) }
复制代码

结果:

next(true)
completed
复制代码
  • and:验证发出的每一个值都为真
Observable.of(true, true)
	.and()
	.subscribe { print($0) }

Observable.of(true, false)
	.and()
	.subscribe { print($0) }

Observable<Bool>.empty()
	.and()
	.subscribe { print($0) }
复制代码

结果:

success(true)
success(false)
completed
复制代码
  • cascade:顺序级联经过一系列可观察对象,当一个可观察对象在列表的更下方开始发射元素时,当即放弃以前的订阅。
let a = PublishSubject<String>()
let b = PublishSubject<String>()
let c = PublishSubject<String>()
Observable.cascade([a,b,c])
    .subscribe { print($0) }
a.onNext("a:1")
a.onNext("a:2")
b.onNext("b:1")
a.onNext("a:3")
c.onNext("c:1")
a.onNext("a:4")
b.onNext("b:4")
c.onNext("c:2")
复制代码

结果:

next(a:1)
next(a:2)
next(b:1)
next(c:1)
next(c:2)
复制代码
  • pairwise:将一个可观察对象发出的元素分组成数组,其中每一个数组由最后两个连续的项组成;相似于滑动窗口。
Observable.from([1, 2, 3, 4, 5, 6])
    .pairwise()
    .subscribe { print($0) }
复制代码

结果:

next((1, 2))
next((2, 3))
next((3, 4))
next((4, 5))
next((5, 6))
completed
复制代码
  • nwise:将一个可观察对象发出的元素分组成数组,其中每一个数组由最后的N个连续项组成;相似于滑动窗口。
Observable.from([1, 2, 3, 4, 5, 6])
    .nwise(3)
    .subscribe { print($0) }
复制代码

结果:

next([1, 2, 3])
next([2, 3, 4])
next([3, 4, 5])
next([4, 5, 6])
completed
复制代码
  • retry:在发生错误或成功终止以前,使用给定的行为重复源观察到的序列。有四种具备不一样谓词和延迟选项的行为:immediate、delayed、exponentialDelayed和customTimerDelayed。
// in case of an error initial delay will be 1 second,
// every next delay will be doubled
// delay formula is: initial * pow(1 + multiplier, Double(currentAttempt - 1)), so multiplier 1.0 means, delay will doubled
_ = sampleObservable.retry(.exponentialDelayed(maxCount: 3, initial: 1.0, multiplier: 1.0), scheduler: delayScheduler)
    .subscribe(onNext: { event in
        print("Receive event: \(event)")
    }, onError: { error in
        print("Receive error: \(error)")
    })
复制代码

结果:

Receive event: First
Receive event: Second
Receive event: First
Receive event: Second
Receive event: First
Receive event: Second
Receive error: fatalError
复制代码
  • repeatWithBehavior:当源观察序列完成时,使用给定的行为重复它。此操做符接受与重试操做符相同的参数。有四种具备不一样谓词和延迟选项的行为:immediate、delayed、exponentialDelayed和customTimerDelayed。
// when the sequence completes initial delay will be 1 second,
// every next delay will be doubled
// delay formula is: initial * pow(1 + multiplier, Double(currentAttempt - 1)), so multiplier 1.0 means, delay will doubled
_ = completingObservable.repeatWithBehavior(.exponentialDelayed(maxCount: 3, initial: 1.0, multiplier: 1.2), scheduler: delayScheduler)
    .subscribe(onNext: { event in
        print("Receive event: \(event)")
})
复制代码

结果:

Receive event: First
Receive event: Second
Receive event: First
Receive event: Second
Receive event: First
Receive event: Second
复制代码
  • catchErrorJustComplete:当发生错误时,取消错误条件,完成一个序列
let _ = sampleObservable
    .do(onError: { print("Source observable emitted error \($0), ignoring it") })
    .catchErrorJustComplete()
    .subscribe {
        print ("\($0)")
}
复制代码

结果:

next(First)
next(Second)
Source observable emitted error fatalError, ignoring it
completed
复制代码
  • pausable:暂停源观察序列的元素,除非来自第二个观察序列的最新元素为真。
let observable = Observable<Int>.interval(1, scheduler: MainScheduler.instance)

let trueAtThreeSeconds = Observable<Int>.timer(3, scheduler: MainScheduler.instance).map { _ in true }
let falseAtFiveSeconds = Observable<Int>.timer(5, scheduler: MainScheduler.instance).map { _ in false }
let pauser = Observable.of(trueAtThreeSeconds, falseAtFiveSeconds).merge()

let pausedObservable = observable.pausable(pauser)

let _ = pausedObservable
    .subscribe { print($0) }
复制代码

结果:

next(2)
next(3)
复制代码
  • apply:Apply为在可观察的序列上应用转换提供了一种统一的机制,而没必要扩展ObservableType或重复您的转换。更多的理由见github上的讨论
// An ordinary function that applies some operators to its argument, and returns the resulting Observable
func requestPolicy(_ request: Observable<Void>) -> Observable<Response> {
    return request.retry(maxAttempts)
        .do(onNext: sideEffect)
        .map { Response.success }
        .catchError { error in Observable.just(parseRequestError(error: error)) }

// We can apply the function in the apply operator, which preserves the chaining style of invoking Rx operators
let resilientRequest = request.apply(requestPolicy)
复制代码
  • filterMap:Rx中的一个常见模式是过滤掉一些值,而后将其他的值映射到其余值。filterMap容许你一步完成:
// keep only even numbers and double them
Observable.of(1,2,3,4,5,6)
	.filterMap { number in
		(number % 2 == 0) ? .ignore : .map(number * 2)
	}
复制代码

上面的序列保持偶数二、四、6,并产生序列四、八、12。

  • errors, elements:这些操做符只适用于使用materialize()操做符(来自RxSwift core)物化的可观察序列。错误返回一个通过过滤的错误事件序列,即抛出的元素。元素返回一个通过过滤的元素事件序列,抛出错误。
let imageResult = _chooseImageButtonPressed.asObservable()
    .flatMap { imageReceiver.image.materialize() }
    .share()

let image = imageResult
    .elements()
    .asDriver(onErrorDriveWith: .never())

let errorMessage = imageResult
    .errors()
    .map(mapErrorMessages)
    .unwrap()
    .asDriver(onErrorDriveWith: .never())
复制代码
  • fromAsync:将简单的异步完成处理程序转换为可观察的序列。适合与仅使用一个参数调用完成处理程序的现有异步服务一块儿使用。发出由完成处理程序生成的结果,而后完成。
func someAsynchronousService(arg1: String, arg2: Int, completionHandler:(String) -> Void) {
    // a service that asynchronously calls
	// the given completionHandler
}

let observableService = Observable
    .fromAsync(someAsynchronousService)

observableService("Foo", 0)
    .subscribe(onNext: { (result) in
        print(result)
    })
    .disposed(by: disposeBag)
复制代码
  • zip(with:):便利版的Observable.zip(_:)。将指定的可观察序列合并为一个可观察序列,只要全部的可观察序列在相应的索引处产生一个元素,就使用选择器函数。
let first = Observable.from(numbers)
let second = Observable.from(strings)

first.zip(with: second) { i, s in
        s + String(i)
    }.subscribe(onNext: { (result) in
        print(result)
    })
复制代码

结果:

next("a1")
next("b2")
next("c3")
复制代码
  • merge(with:):便利版的Observable.merge(_:)。将可观察序列中的元素与不一样的可观察序列中的元素合并为一个可观察序列。
let oddStream = Observable.of(1, 3, 5)
let evenStream = Observable.of(2, 4, 6)
let otherStream = Observable.of(1, 5, 6)

oddStream.merge(with: evenStream, otherStream)
    .subscribe(onNext: { result in
        print(result)
    })
复制代码

结果:

1 2 1 3 4 5 5 6 6
复制代码
  • ofType:ofType操做符过滤可观察序列的元素(若是它是提供的类型的实例)。
Observable.of(NSNumber(value: 1),
                  NSDecimalNumber(string: "2"),
                  NSNumber(value: 3),
                  NSNumber(value: 4),
                  NSDecimalNumber(string: "5"),
                  NSNumber(value: 6))
        .ofType(NSDecimalNumber.self)
        .subscribe { print($0) }
复制代码

结果:

next(2)
next(5)
completed
复制代码
  • withUnretained:withunretain (_:resultSelector:)操做符提供了一个未保留的、能够安全使用(即不隐式取消包装)的对象引用,以及序列发出的事件。若是提供的对象不能成功保留,则seqeunce将完成
class TestClass: CustomStringConvertible {
    var description: String { return "Test Class" }
}

Observable
    .of(1, 2, 3, 5, 8, 13, 18, 21, 23)
    .withUnretained(testClass)
    .do(onNext: { _, value in
        if value == 13 {
            // When testClass becomes nil, the next emission of the original
            // sequence will try to retain it and fail. As soon as it fails,
            // the sequence will complete.
            testClass = nil
        }
    })
    .subscribe()
复制代码

结果:

next((Test Class, 1))
next((Test Class, 2))
next((Test Class, 3))
next((Test Class, 5))
next((Test Class, 8))
next((Test Class, 13))
completed
复制代码
  • count:在一个可观察对象终止且没有错误时发出的项数。若是给定一个谓词,则只计算与谓词匹配的元素。
Observable.from([1, 2, 3, 4, 5, 6])
    .count { $0 % 2 == 0 }
    .subscribe()
复制代码

结果:

next(3)
completed
复制代码
  • partition:将一个流划分为两个单独的元素流,这两个元素流与提供的谓词匹配或不匹配。
let numbers = Observable
        .of(1, 2, 3, 4, 5, 6)

    let (evens, odds) = numbers.partition { $0 % 2 == 0 }

    _ = evens.debug("even").subscribe() // emits 2, 4, 6
    _ = odds.debug("odds").subscribe() // emits 1, 3, 5
复制代码
  • bufferWithTrigger:收集源可观察到的元素,并在触发器发出时将它们做为数组发出。
let observable = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
let signalAtThreeSeconds = Observable<Int>.timer(3, scheduler: MainScheduler.instance).map { _ in () }
let signalAtFiveSeconds = Observable<Int>.timer(5, scheduler: MainScheduler.instance).map { _ in () }
let trigger = Observable.of(signalAtThreeSeconds, signalAtFiveSeconds).merge()
let buffered = observable.bufferWithTrigger(trigger)
buffered.subscribe { print($0) }
// prints next([0, 1, 2]) @ 3, next([3, 4]) @ 5
复制代码
2.2.3.3 NSObject+Rx

若是你用Rxswift通常你常常须要这样子let disposeBag = DisposeBag()定义一个垃圾袋对象,用来销毁回收序列的资源。每一个类中都要去定义这样一个东东是很麻烦的。而NSObject+Rx帮你简化了这部操做,你能够不须要定义let disposeBag = DisposeBag()这样的代码了,直接ob.rx.disposeBag就能够了,例如:

thing
  .bind(to: otherThing)
  .disposed(by: rx.disposeBag)
复制代码
  • 安装方式: CocoaPods

Add to your Podfile:

pod 'NSObject+Rx'
复制代码

Carthage

Add to Cartfile:

github "RxSwiftCommunity/NSObject-Rx"
复制代码
2.2.3.4 RxViewController

RxViewController是用于UIViewController和NSViewController的RxSwift包装器。

有了RxViewController的包装后,你能够这样在VC中调用viewDidLoad方法:

self.rx.viewDidLoad
  .subscribe(onNext: {
    print("viewDidLoad 🎉")
  })
复制代码

此外RxViewController还提供了如下这些API:

extension Reactive where Base: UIViewController {
  var viewDidLoad: ControlEvent<Void>

  var viewWillAppear: ControlEvent<Bool>
  var viewDidAppear: ControlEvent<Bool>

  var viewWillDisappear: ControlEvent<Bool>
  var viewDidDisappear: ControlEvent<Bool>

  var viewWillLayoutSubviews: ControlEvent<Void>
  var viewDidLayoutSubviews: ControlEvent<Void>

  var willMoveToParentViewController: ControlEvent<UIViewController?>
  var didMoveToParentViewController: ControlEvent<UIViewController?>

  var didReceiveMemoryWarning: ControlEvent<Void>
}
复制代码
2.2.3.5 RxGesture

RxGesture星星
RxGesture可让你轻松地将任何视图变成可移动或可滑动的控件,就像这样:

view.rx
  .tapGesture()
  .when(.recognized)
  .subscribe(onNext: { _ in
    //react to taps
  })
  .disposed(by: stepBag)
复制代码

你也能够对多种手势作出反应。例如,当用户点击或上下滑动照片预览时,你可能想要关闭它:

view.rx
  .anyGesture(.tap(), .swipe([.up, .down]))
  .when(.recognized)
  .subscribe(onNext: { _ in
    //dismiss presented photo
  })
  .disposed(by: stepBag)
复制代码

rx.gesture被定义为Observable<G>其中G是手势识别器的实际类型因此它发出的是手势识别器自己(若是想调用asLocation(in view:)asTranslation(in view:)这样的方法很方便)

RxGesture支持以下手势:

view.rx.tapGesture()           -> ControlEvent<UITapGestureRecognizer>
view.rx.pinchGesture()         -> ControlEvent<UIPinchGestureRecognizer>
view.rx.swipeGesture(.left)    -> ControlEvent<UISwipeGestureRecognizer>
view.rx.panGesture()           -> ControlEvent<UIPanGestureRecognizer>
view.rx.longPressGesture()     -> ControlEvent<UILongPressGestureRecognizer>
view.rx.rotationGesture()      -> ControlEvent<UIRotationGestureRecognizer>
view.rx.screenEdgePanGesture() -> ControlEvent<UIScreenEdgePanGestureRecognizer>

view.rx.anyGesture(.tap(), ...)           -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.pinch(), ...)         -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.swipe(.left), ...)    -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.pan(), ...)           -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.longPress(), ...)     -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.rotation(), ...)      -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.screenEdgePan(), ...) -> ControlEvent<UIGestureRecognizer>
复制代码

若是您单独使用手势识别器,请选择view.rx.fooGesture()语法而不是view.rx.anyGesture(.foo()),由于它返回具体的UIGestureRecognizer子类,并避免您将其转换为subscribe()

  • RxGesture 手势过滤: 默认状况下,手势识别器的状态没有过滤器。这意味着您将始终接收到带有手势识别器初始状态的第一个事件(几乎老是.possible)。

默认状况下,手势识别器的状态没有过滤器。这意味着,这里有能够用于各类手势(iOS和macOS)的首选状态:

手势过滤
一般使用.when()操做符过滤状态:

view.rx.tapGesture().when(.recognized)
view.rx.panGesture().when(.began, .changed, .ended)
复制代码

若是你同时观察多个手势,你可使用when()操做符,若是你想过滤全部手势识别器的相同状态,或者使用tuple语法进行单独的过滤:

view.rx
  .anyGesture(.tap(), .swipe([.up, .down]))
  .when(.recognized)
  .subscribe(onNext: { gesture in
    // Called whenever a tap, a swipe-up or a swipe-down is recognized (state == .recognized)
  })
  .disposed(by: bag)

view.rx
  .anyGesture(
    (.tap(), when: .recognized),
    (.pan(), when: .ended)
  )
  .subscribe(onNext: { gesture in
    // Called whenever:
    // - a tap is recognized (state == .recognized)
    // - or a pan is ended (state == .ended)
  })
  .disposed(by: bag)
复制代码

这里有一个官方的演示应用程序包括全部识别器的例子: ➡️ iOS, macOS.

每一个手势识别器都有一个默认的RxGestureRecognizerDelegate。它容许你使用一个策略自定义每一个委托方法:

  1. .always : 对应的委托方法是否返回true
  2. .never : 将返回false到相应的委托方法
  3. .custom : 获取将执行的关联闭包,以将值返回给相应的委托方法

如下是可用的策略及其相应的委托方法:

beginPolicy                   -> gestureRecognizerShouldBegin(:_)
touchReceptionPolicy          -> gestureRecognizer(_:shouldReceive:)
selfFailureRequirementPolicy  -> gestureRecognizer(_:shouldBeRequiredToFailBy:)
otherFailureRequirementPolicy -> gestureRecognizer(_:shouldRequireFailureOf:)
simultaneousRecognitionPolicy -> gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)
eventRecognitionAttemptPolicy -> gestureRecognizer(_:shouldAttemptToRecognizeWith:) // macOS only
pressReceptionPolicy          -> gestureRecognizer(_:shouldReceive:) // iOS only
复制代码

这个委托能够在配置包中定制:

view.rx.tapGesture(configuration: { gestureRecognizer, delegate in
  delegate.simultaneousRecognitionPolicy = .always // (default value)
  // or
  delegate.simultaneousRecognitionPolicy = .never
  // or
  delegate.simultaneousRecognitionPolicy = .custom { gestureRecognizer, otherGestureRecognizer in
    return otherGestureRecognizer is UIPanGestureRecognizer
  }
  delegate.otherFailureRequirementPolicy = .custom { gestureRecognizer, otherGestureRecognizer in
    return otherGestureRecognizer is UILongPressGestureRecognizer
  }
})
复制代码

默认值能够在RxGestureRecognizerDelegate.swift中找到。

  • RxGesture 次外还支持彻底自定义方式:

您还能够用本身的委托替换默认委托,或者删除它。代码以下:

view.rx.tapGesture { [unowned self] gestureRecognizer, delegate in
  gestureRecognizer.delegate = nil
  // or
  gestureRecognizer.delegate = self
}
复制代码
  • 安装方式: CocoaPods

Add this to Podfile

pod "RxGesture"
复制代码

$ pod install Carthage

Add this to Cartfile

github "RxSwiftCommunity/RxGesture" ~> 3.0
复制代码

$ carthage update

2.2.3.6 RxOptional
  • 源码下载: RxOptional
    RxOptional星星
    RxOptional适用于Swift选项和“可占用”类型的RxSwift扩展。

除另有说明外,全部操做符也可用于驱动程序和信号。

  • 可选操做: filterNil的用法:
Observable<String?>
    .of("One", nil, "Three")
    .filterNil()
    // Type is now Observable<String>
    .subscribe { print($0) }
复制代码

结果打印:

next(One)
next(Three)
completed
复制代码

replaceNilWith 的用法:

Observable<String?>
    .of("One", nil, "Three")
    .replaceNilWith("Two")
    // Type is now Observable<String>
    .subscribe { print($0) }
复制代码

打印结果:

next(One)
next(Two)
next(Three)
completed
复制代码

errorOnNil 的用法:

注意:在驱动程序上不可用,由于驱动程序不能出错。 默认状况下,rxoptionalerror . foundnilwhile eunwrappingoptional有错误。

Observable<String?>
    .of("One", nil, "Three")
    .errorOnNil()
    // Type is now Observable<String>
    .subscribe { print($0) }
复制代码

结果打印:

next(One)
error(Found nil while trying to unwrap type <Optional<String>>)
复制代码

catchOnNil 的用法:

Observable<String?>
    .of("One", nil, "Three")
    .catchOnNil {
        return Observable<String>.just("A String from a new Observable")
    }
    // Type is now Observable<String>
    .subscribe { print($0) }
复制代码

打印结果:

next(One)
next(A String from a new Observable)
next(Three)
completed
复制代码

distinctUntilChanged 的用法:

Observable<Int?>
    .of(5, 6, 6, nil, nil, 3)
    .distinctUntilChanged()
    .subscribe { print($0) }

复制代码

打印结果:

next(Optional(5))
next(Optional(6))
next(nil)
next(Optional(3))
completed
复制代码
  • 占位操做主要有:
  1. String
  2. Array
  3. Dictionary
  4. Set

目前在Swift协议中不能扩展到符合其余协议。目前,上面列出的类型符合Occupiable。您还可使自定义类型符合Occupiable。

filterEmpty 的用法:

Observable<[String]>
    .of(["Single Element"], [], ["Two", "Elements"])
    .filterEmpty()
    .subscribe { print($0) }
复制代码

打印结果:

next(["Single Element"])
next(["Two", "Elements"])
completed
复制代码

errorOnEmpty的用法:

在驱动程序上不可用,由于驱动程序不能出错。 默认状况下,RxOptionalError.emptyOccupiable会出现错误。

Observable<[String]>
    .of(["Single Element"], [], ["Two", "Elements"])
    .errorOnEmpty()
    .subscribe { print($0) }
复制代码

打印结果:

next(["Single Element"])
error(Empty occupiable of type <Array<String>>)
复制代码

catchOnEmpty 的用法:

Observable<[String]>
    .of(["Single Element"], [], ["Two", "Elements"])
    .catchOnEmpty {
        return Observable<[String]>.just(["Not Empty"])
    }
    .subscribe { print($0) }
复制代码

打印结果:

next(["Single Element"])
next(["Not Empty"])
next(["Two", "Elements"])
completed
复制代码
  • 安装方式:

CocoaPods

RxOptional能够经过CocoaPods得到。要安装它,只需将如下行添加到您的Podfile中:

pod 'RxOptional'
复制代码

Carthage

将此添加到Cartfile

github "RxSwiftCommunity/RxOptional" ~> 4.1.0
复制代码

$ carthage update

2.2.3.7 RxTheme

RxTheme基于Rx的主题管理扩展框架

  • 安装方式: Cocoapods
pod 'RxTheme', '~> 4.0'
复制代码

Carthage

github "RxSwiftCommunity/RxTheme" ~> 4.0.0
复制代码

经过RxTheme 你能够这样定义app 的主题服务:

import RxTheme

protocol Theme {
    var backgroundColor: UIColor { get }
    var textColor: UIColor { get }
}

struct LightTheme: Theme {
    let backgroundColor = .white
    let textColor = .black
}

struct DarkTheme: Theme {
    let backgroundColor = .black
    let textColor = .white
}

enum ThemeType: ThemeProvider {
    case light, dark
    var associatedObject: Theme {
        switch self {
        case .light:
            return LightTheme()
        case .dark:
            return DarkTheme()
        }
    }
}

let themeService = ThemeType.service(initial: .light)
复制代码
  • 将主题应用到UI
// Bind stream to a single attribute
// In the way, RxTheme would automatically manage the lifecycle of the binded stream 
view.theme.backgroundColor = themeService.attrStream { $0.backgroundColor }

// Or bind a bunch of attributes, add them to a disposeBag
themeService.rx
    .bind({ $0.textColor }, to: label1.rx.textColor, label2.rx.textColor)
    .bind({ $0.backgroundColor }, to: view.rx.backgroundColor)
    .disposed(by: disposeBag)
复制代码

全部由ThemeService生成的流都是共享的(1)

  • 你能够很轻松的实现换肤,切换主题的功能,只须要一行代码搞定:
themeService.switch(.dark)
// When this is triggered by some signal, you can use:
someSignal.bind(to: themeService.switcher)
复制代码

此外RxTheme还提供了下面的一些API:

// Current theme type
themeService.type
// Current theme attributes
themeService.attrs
// Theme type stream
themeService.typeStream
// Theme attributes stream
themeService.attrsStream
复制代码
  • 已经实现预设的绑定器有: CALayer

backgroundColor borderWidth borderColor shadowColor

CAShapeLayer:

strokeColor fillColor

UIActivityIndicatorView

style

UIBarButtonItem

tintColor

UIButton

titleColor

UILabel

font textColor highlightedTextColor shadowColor

UINavigationBar

barStyle barTintColor titleTextAttributes

UIPageControl

pageIndicatorTintColor currentPageIndicatorTintColor

UIProgressView

progressTintColor trackTintColor

UISearchBar

barStyle barTintColor keyboardAppearance

UISlider

thumbTintColor minimumTrackTintColor maximumTrackTintColor

UISwitch

onTintColor thumbTintColor

UITabBar

barStyle barTintColor

UITableView

separatorColor

UITAbleViewCell

selectionStyle

UITextField

font textColor keyboardAppearance

UITextView

font textColor keyboardAppearance

UIToolbar

barStyle barTintColor

UIView

tintColor

  • 你还能够选择本身扩展代码库中的绑定: 由于RxTheme使用来自RxCocoaBinder<T>,因此RxCocoa中定义的任何Binder均可以在这里使用。 这也使得库超级容易在你的代码库中扩展,下面是一个例子:
extension Reactive where Base: UIView {
    var borderColor: Binder<UIColor?> {
        return Binder(self.base) { view, color in
            view.layer.borderColor = color?.cgColor
        }
    }
}
复制代码

若是您还想使用sugar view.theme。边界颜色,你必须写另外一个扩展:

extension ThemeProxy where Base: UIView {
    var borderColor: Observable<UIColor?> {
        get { return .empty() }
        set {
            let disposable = newValue
                .takeUntil(base.rx.deallocating)
                .observeOn(MainScheduler.instance)
                .bind(to: base.rx.borderColor)
            hold(disposable, for: "borderColor")
        }
    }
}
复制代码
2.2.3.8 RxAnimated

2.2.4 图像处理库

2.2.4.1 Kingfisher

2.2.5 资源文件管理库

2.2.5.1 R.swift
2.2.5.2 SwiftLint

2.2.6 秘钥管理库

2.2.6.1 KeychainAccess

2.2.7 自动布局库

2.2.7.1 SnapKit

2.2.8 UI相关库

2.2.8.1 NVActivityIndicatorView
2.2.8.2 ImageSlidershow/Kingfisher
2.2.8.3 DZNEmptyDataSet
2.2.8.4 Hero
  • 源码下载:Hero
2.2.8.5 Localize-Swift
2.2.8.6 RAMAnimatedTabBarController
2.2.8.7 AcknowList
2.2.8.8 KafkaRefresh
2.2.8.9 WhatsNewKit
2.2.8.10 Highlightr
2.2.8.11 DropDown
2.2.8.12 Toast-Swift
2.2.8.13 HMSegmentedControl
2.2.8.14 FloatingPanel
2.2.8.15 MessageKit
2.2.8.16 MultiProgressView
2.2.8.17 IQKeyboardManagerSwift

2.2.9 日志管理库

2.2.9.1 CocoaLumberjack/Swift

2.2.10 数据埋点库

2.2.10.1 Umbrella
2.2.10.2 Umbrella/Mixpanel
2.2.10.3 Umbrella/Firebase
2.2.10.4 Mixpanel
2.2.10.5 Firebace/Analytics

2.2.11 广告工具点库

2.2.11.1 Firebase/AdMob
2.2.11.2 Google-Mobile-Ads-SDK

2.2.12 性能优化相关库

2.2.12.1 Fabric
2.2.12.2 Crashlytics

2.2.13 其余工具类库

2.2.13.1 FLEX
  • 源码下载: FLEX
2.2.13.2 SwifterSwift
2.2.13.3 BonMot
2.2.13.4 DateToolsSwift
2.2.13.5 SwiftDate

3. SwiftHub项目采用的架构分析

参考:www.jianshu.com/p/fb63ca356…

相关文章
相关标签/搜索