開源項目分析(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…

相關文章
相關標籤/搜索