Open Source Project Analysis (SwiftHub) Rxswift + MVVM + Moya Architecture Analysis (I)

Open Source Project Analysis (SwiftHub) Rxswift + MVVM + Moya Architecture Analysis (II) Use of third-party frameworks (IN)

Open source project analysis (SwiftHub) Rxswift + MVVM + Moya architecture analysis (A) third-party framework use

1. SwiftHub Project Introduction

SwiftHub is a project written by Khoren Markosyan that completely adopts the architecture of Rxswift + MVVM + Moya. The code is very simple. If you want to learn the architecture of MVVM and carefully study the design of this project, it will be of great help to your future programming ideas and habits. (Click here to download SwiftHub.)

1.1 SwiftHub PROJECT UI

1.2 SwiftHub code structure

SwiftHub project compilation, used by the third party library introduction

2.1 SwiftHub Project Compilation

  • Click here to download SwiftHub source code

Swifthub-master/pod Install swifthub-master/pod Install

You need to update your local COcos Pod library with a pod repo.

Pod install has not been updated, here provides a copy of my compiled source code: link :pan.baidu.com/s/1qwkjY_Zr… Password: 60 t7 has

2.2 Third-party frameworks used by SwiftHub

  • I was like, wow, there are so many third party frameworks. My personal view is that I’m not a big advocate of using too many third party frameworks and implementing everything I can, unless the functionality that I want to implement has to be implemented in a third party framework. For example, we had a project that used Realm as the DB framework, but it took up too much memory, nearly 90MB, and I just wanted a small database to store the relevant code. Swift framework, this framework is only about 2MB.

  • Let’s take a look at the third-party frameworks used by 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

Copy the code
  • Next, let’s take a look at what these third-party libraries are used for, in case you need them for your project one day.

2.2.1 network library

2.2.1.1 Alamofire

Alamofire and AFNetwork are brothers and products of the same company. It is a good network framework library written by Swift, providing HTTP related interfaces, which can easily realize chain request and response, file upload, download, breakpoint continuation, background download and other functions.

Installation method:

  • CocoaPods installation:Pod 'Alamofire', '~ > 5.1'
  • Carthage installation:Making "Alamofire/Alamofire" ~ > 5.1

Installation environment requirements:

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

Functions and features:

  1. Chainable request/response methods
  2. URL/JSON parameter encoding
  3. Upload file/data/stream/MultipartFormData
  4. Download files using request or resume data
  5. Authentication and URLCredential
  6. HTTP Response Validation
  7. Upload and download progress closures with progress
  8. CURL command output
  9. Adjust and retry requests dynamically
  10. The TLS certificate and public key are fixed
  11. Network accessibility
  12. Comprehensive unit and integration test coverage
  13. Complete documentation

In order to make Alamofire more focused on handling web-related matters, the Alamofire Software Foundation has created additional component libraries to bring additional functionality to the Alamofire ecosystem. Such as: AlamofireImage libraries and AlamofireNetworkActivityIndicator

  1. AlamofireImage: An image library that includes image response serializer, UIImage and UIImageView extensions, custom image filters, an auto-clear memory cache and a priority-based image download system.
  2. AlamofireNetworkActivityIndicator: use Alamofire control the visibility of iOS on the network activity indicator. It includes configurable delay timers to help reduce flickering, and supports URLSession instances that are not managed by Alamofire.
  • Alamofire frame structure diagram:

  • Check out some of my blogs for Alamofire usage:

Alamofire learning (I) Network foundation

Alamofire (2) URLSession

Alamofire (3) Background download principle

  • Network request steps:
  1. Setting the request URL
  2. Set the URLRequest object and configure the request information
  3. Example Create a session configuration URLSessionConfiguration
  4. Create session URLSession
  5. Create the task and set the request callback, and initiate the request

Simple request code:

func responseData(a) {
    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}}}Copy the code

URLSessionConfiguration provides configuration related to the framework:

The following three methods are mainly provided:

  1. default: Default mode: common mode. In this mode, the system creates a persistent cache and stores the certificate in the user’s key string
  2. ephemeral: Does not support persistent storage. All content is released at the end of the session lifebackground: Similar to the default mode, in this mode, an independent thread is created to transfer network request data. Data can be transferred in the background or even when the APP is closed
  • Create a session:
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()
Copy the code

A number of properties are also provided to configure as needed

  • General attributes:
  1. identifier: Configures the background session identifier of the object
  2. httpAdditionalHeaders: Dictionary of additional header files sent with the request
  3. networkServiceType: Indicates the network service type
  4. allowsCellularAccess: A Boolean value for whether to connect over a cellular network
  5. timeoutIntervalForRequest: Timeout period for waiting for additional data
  6. timeoutIntervalForResource: Maximum time range allowed for resource requests
  7. sharedContainerIdentifier: Identifier of the shared container to which files in the background URL session should be downloaded
  8. waitsForConnectivity: A Boolean value indicating whether the session should wait for the connection to become available or fail immediately
  • Set the Cookie policy:
  1. httpCookieAcceptPolicy: Policy constant that determines when cookies are accepted
  2. httpShouldSetCookies: a Boolean value that determines whether the request contains cookies from the cookie store
  3. httpCookieStorage: Cookie store used to store cookies in the session
  4. HTTPCookie: This object is immutable, initialized from the dictionary containing the cookie attribute, and supports two different cookie versions, v0 and v1
  • Setting security policies:
  1. The TLS protocol: Used to provide confidentiality and data integrity between two communication applications
  2. tlsMaximumSupportedProtocol: The maximum TLS protocol version requested by the client when establishing a connection in this session
  3. tlsMinimumSupportedProtocol: Protocol Indicates the minimum TLS protocol to be accepted during the negotiation
  4. urlCredentialStorage: Credential store that provides credentials for authentication
  • Set the cache policy:
  1. urlCache: THE URL cache used to provide cached responses to requests in the session
  2. requestCachePolicy: a predefined constant that determines when the response is returned from the cache
  • Support background mode:
  1. sessionSendsLaunchEvents: A Boolean value indicating whether the application should resume or start in the background when the transfer is complete
  2. isDiscretionary: A Boolean value that determines whether background tasks can be scheduled by the system for optimal performance
  3. shouldUseExtendedBackgroundIdleMode: A Boolean value indicating whether the TCP connection should be kept open while the application moves to the background
  • Supports user-defined protocols
  1. protocolClasses: An array of additional protocol subclasses that process requests in the session
  2. URLProtocol: this object is used to process load protocol specific URL data
  • Supports multipathing TCP:
  1. multipathServiceType: Specifies the service type of the multipath TCP connection policy used to transmit data over Wi-Fi and cellular interfaces
  • Set HTTP policy and proxy properties:
  1. httpMaximumConnectionsPerHost: Indicates the maximum number of simultaneous connections to a given host
  2. httpShouldUsePipelining: A Boolean value that determines whether the session uses HTTP pipelining
  3. connectionProxyDictionary: contains a dictionary of information about agents to be used in this session
  • Support for connection changes:
  1. waitsForConnectivity: A Boolean value indicating whether the session should wait for a connection to become available or fail immediately

In addition, here are some simple basic functions:

  • Chain response
Alamofire.request("https://www.baidu.com").responseString { (response) in
            print("Response String: \(String(describing: response.result.value))")
            }.responseJSON { (response) in
                print("Response JSON:\(String(describing: response.result.value)")}Copy the code
  • addHeadersandparameter
let parameters: Parameters = [
            "foo": "bar"."baz": ["a".1]."qux": [
                "x": 1."y": 2."z": 3]]let headers: HTTPHeaders = [
            "Accept" : "application/json"."Authorization" : "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
        ]
        Alamofire.request("https://www.baid.com/post", method: .post, parameters: parameters, encoding: URLEncoding.httpBody, headers: headers)
Copy the code
  • Status code Filtering
Alamofire.request("https://www.baid.com/get")
        .validate(statusCode: 200..<300)
        .validate(contentType: ["application/json"])
            .responseData { (response) in
                switch response.result {
                case .success:
                    print("successful")
                case .failure:
                    print("error")}Copy the code
  • Upload a file
        let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")!
        Alamofire.upload(fileURL, to: "https://www.baid.com/post")
            .uploadProgress { progress in // main queue by default
                print("Upload Progress: \(progress.fractionCompleted)")
            }
            .downloadProgress { progress in // main queue by default
                print("Download Progress: \(progress.fractionCompleted)")
            }
            .responseJSON { response in
                debugPrint(response)
        }
Copy the code
  • Upload Data
let imageData = UIImagePNGRepresentation(image)!
      
        Alamofire.upload(imageData, to: "https://www.baid.com/post").responseJSON { response in
            debugPrint(response)
        }
Copy the code
  • File download
 Alamofire.download("https://www.baid.com/image/png")
            .downloadProgress { progress in
                print("Download Progress: \(progress.fractionCompleted)")
            }
            .responseData { response in
                if let data = response.result.value {
                    let image = UIImage(data: data)
                }
        }
Copy the code
2.2.1.2 Rxswift

Rxswift family provides a very good functional responsive programming framework, using Rxswift to write code can make the code become very simple, logical, if combined with Moya + Rxswift + MVVM architecture, it is really perfect, this open source project SwiftHub is such a perfect project.

ReactiveX (Rx) is a framework that helps simplify asynchronous programming. And RxSwift is the Swift version of Rx. In addition to RxSwift, there are RxJava, RxJS, Rx.Net, etc., the corresponding OC version is RAC(ReactiveCocoa), here is the GIthub address of RxSwift, has nearly 18.2k stars.

RxSwift: The core of RxSwift, providing the Rx standard defined (mainly) by ReactiveX. It has no other dependencies. RxCocoa: Provides Cocoa-specific functionality such as bindings, features, and so on for general iOS/macOS/watchOS & tvOS application development. It relies on both RxSwift and RxRelay. RxRelay: Provides publication relay and behavior Relay, two simple topic wrappers. It depends on RxSwift. RxTest and RxBlocking: Provides testing capabilities for Rx-based systems. It depends on RxSwift.

  • For more information on how to use Rxswift, see my blog:

Rxswift (1) Functional responsive programming idea

RxSwift (II) Sequence core logic analysis

Create, subscribe, and destroy an RxSwift Observable

RxSwift (4) higher-order functions

RxSwift (v)

(6) Dispose source code analysis

RxSwift (7) RxSwift vs. SWIFT usage

RxSwift (x) Basic Usage part 1- Sequence, subscribe, destroy

RxSwift Learning 12 (Basics 3- UI Control Extensions)

Some simple uses of Rxswift are as follows:

  • Button Click event:
//MARK: - RxSwift application -button response
func setupButton(a) {
    // Traditional UI events
    self.button.addTarget(self, action: #selector(didClickButton), for: .touchUpInside)

    // This is not the case. Code logic is layered with event logic
    self.button.rx.tap
        .subscribe(onNext: { [weak self] in
            print("Yes, chicken and mushroom stew.")
            self? .view.backgroundColor =UIColor.orange   
        })
        .disposed(by: disposeBag) 
}
Copy the code
  • Textfiled Text response
//MARK: -rxSwift -textFiled
func setupTextFiled(a) {
    // If we want to operate on the input text - such as the input content and then we get the even number inside
    // self.textFiled.delegate = self
    // Do you feel sick
    // Let's look at Rx
    self.textFiled.rx.text.orEmpty.changed.subscribe(onNext: { (text) in
        print("Got a wire\(text)")
    }).disposed(by: disposeBag)

    self.textFiled.rx.text.bind(to: self.button.rx.title()).disposed(by: disposeBag)
}
Copy the code
  • Use scrollView
//MARK: -rxSwift application -scrollView
func setupScrollerView(a) {
    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)
}
Copy the code
  • KVO
 //MARK: -rxSwift -kvo
func setupKVO(a) {
    // System KVO is still quite troublesome
    // 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)
}
Copy the code
  • notice
/ / MARK: - notice
func setupNotification(a){
    NotificationCenter.default.rx
        .notification(UIResponder.keyboardWillShowNotification)
        .subscribe { (event) in
            print(event)
    }.disposed(by: disposeBag)
}
Copy the code
  • gestures
/ / MARK: - hand gestures
func setupGestureRecognizer(a){
    let tap = UITapGestureRecognizer(a)self.label.addGestureRecognizer(tap)
    self.label.isUserInteractionEnabled = true
    tap.rx.event.subscribe { (event) in
        print("Point the label")
    }.disposed(by: disposeBag)  
}
Copy the code
  • Network request
//MARK: - RxSwift application - network request funcsetupNextwork() {
    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)
}
Copy the code
  • The timer
//MARK: -rxswift -timer Indicates the timer
func setupTimer(a) {
    timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
    timer.subscribe(onNext: { (num) in
        print("hello word \(num)")
    }).disposed(by: disposeBag)
}
Copy the code
2.2.1.3 Moya
  • Source code download: Moya

Moya is a network abstraction layer that encapsulates Alamofire at the bottom and provides a simpler interface for developers to call. In Objective-C, most developers will use AFNetwork for network requests. When the business becomes more complicated, they will encapsulate AFNetwork twice and write a network abstraction layer suitable for their own projects. In Objective-C, the famous YTKNetwork encapsulates AFNetworking as an abstract parent class, and then writes different subclasses according to different network requests. The subclasses inherit the parent class to realize request services. Moya’s position at the project level is somewhat similar to YTKNetwork. You can see the comparison below

It would be a mistake to simply equate Moya with the swift version of YTKNetwork. The design idea of Moya is very different from that of YTKNetwork. When I introduced YTKNetwork above, I emphasized the subclass, parent class and inheritance, because YTKNetwork is a product of classic OOP (object-oriented) design. Although Swift based Moya also uses inheritance, it is generally dominated by the idea of POP (Protocol Oriented Programming).

  • Moya module composition:

  1. Provider:providerIs a provider of network request services. After some initial configuration, the provider can be used externally to initiate requests directly.
  2. Request: in the use ofMoyaWhen making a network Request, the first step is to configure it to generate a Request. First, create an enumeration according to the official documentationTargetTypeProtocol, and implements the properties specified by the protocol. Why create enumerations to comply with the protocol, enumerations bindingswitchStatement to make the API easier to manage.
  3. A compliance was created based onTargetTypeEnumeration of the protocol named Myservice. We have set the following variables.
	baseURL
	path
	method
	sampleData
	task
	headers
Copy the code
  • Moya use

import UIKit
import Moya
import RxCocoa
import Result
import SwiftyJSON

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

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

/** requested endpoints) **/
// Request classification
enum KNetworkAPI {
    case shareNavList:
    case shareList(pageSize: Int, pageNum: Int):
}
// Request configuration
extension KNetworkAPI: TargetType {
    // Server address
    public var baseURL: URL {
        switch self {
        default:
            return URL(string: K_Search_Base)! }}// The specific path of each request
    public var path: String {
        switch self {
        case .shareNavList:
            return "manage/navigation/getNavigationList"
        default:
            return "default/list"}}// Request type
    public var method: Moya.Method {
        switch self {
       
        default:
            return .get}}// Request task events (with parameters here)
    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)}}// Whether to perform Alamofire validation
    public var validate: Bool {
        return false
    }
    // This is the unit test simulation data,
// only works in unit test files
    public var sampleData: Data {
        return "{}".data(using: String.Encoding.utf8)!
    }

    / / request header
    public var headers: [String: String]? {
        switch self {
        default:
            return ["Content-type": "application/json"]}}}RequestLoadingPluginPlug-ins to displayUIRelated, catch network exceptions and other operations, give hints, the code is as follows: ' 'swiftimport 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("Begin the request.")
        if SwiftIsShowHud= =true {
            let keyViewController = UIApplication.shared.keyWindow? .rootViewControllerif(keyViewController ! =nil) {
                MBProgressHUD.showAdded(to: keyViewController! .view, animated:true)}}}func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
        print("End request")
        let keyViewController = UIApplication.shared.keyWindow? .rootViewControllerif(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>
            
            ifdic ! =nil {
                ifdic? .keys.contains("status") = =true {
                    ifdic? ["status"] as! Int= =11|| dic? ["status"] as! Int= =12 {
                        print("Token failure")}}ifdic? .keys.contains("code") = =true {
                    ifdic? ["code"] as! Int= =11|| dic? ["code"] as! Int= =12 {
                        print("Token failure")}}}return
        }
        let errorReason: String= (result.error? .errorDescription)!print("Request failed:\(errorReason)")
        var tip = ""
        if errorReason.contains("The Internet connection appears to be offline") {
            tip = "Network is not working, please check your network."
        }else if errorReason.contains("Could not connect to the server") {
            tip = "Unable to connect to server"
        }else {
           tip = "Request failed"
        }
        /// Use the tip text to prompt}}Copy the code
  • The call code is as follows:
import RxSwift
import RxCocoa
import ObjectMapper

KApiProvider.rx.request(input.category)
                .mapObject(KBaseModel<T>.self)
                .subscribe(onSuccess: { (baseModel) in
                    print("Successful request returns the following data")
                    ifbaseModel.status ! =0 {
                        return
                    }
                }, onError: {error in
                    print("Error: request Error")
                }).disposed(by: self.disposeBag)
            }, onError: { (error) in
                
        }, onCompleted: {
            
        }) {
            }.disposed(by: disposeBag)


Copy the code
2.2.1.4 MoyaHTTPS certificate trust, self-signed certificate trust

Moya HTTPS certificate trust, self-signed certificate trust

  • In development, the server may use HTTPS. In this case, if the server is using the certificate of the certificate authority, we do not need to do anything when using Moya. If it is a self-signed certificate, we need to perform certificate trust, otherwise the request will be rejected directly.

For those of you who are not familiar with the HTTPS handshake process, it is necessary to understand the HTTPS handshake process to understand the certificate authentication mechanism:

An SSL/TLS handshake is performed before sending an HTTPS request. The handshake process is as follows:

  1. The client initiates a handshake request with parameters such as a random number and a list of supported algorithms.
  2. After receiving the request, the server selects an appropriate algorithm and delivers the public key certificate and random number.
  3. The client verifies the server certificate and sends random number information, which is encrypted using the public key.
  4. The server obtains random number information through the private key.
  5. The session ticket generated by both parties is used as the encryption key for subsequent data transmission of the connection.

In Step 3, the client needs to verify the certificate issued by the server. The verification process includes the following two points:

  1. The client uses the locally saved root certificate to unlock the certificate chain and verify that the certificate issued by the server is issued by a trusted authority.
  2. The client needs to check the domain and extension domains of the certificate to see if they contain the host for this request. If both of the above parameters pass, the current server is trusted; otherwise, it is untrusted and the current connection should be broken.

When a client initiates a request using an IP address, the host in the REQUEST URL is replaced by the IP resolved by the HTTP DNS. Therefore, domain mismatch occurs in step 2 of certificate verification, resulting in an SSL/TLS handshake failure.

For more details, please refer to my previous blog post about HTTPS self-signed certificates uploading and downloading files:

IOS Network Protocol (1) Self-signed certificate HTTPS file Upload and download (1)

IOS Audio and Video (45) HTTPS self-signed certificates implement side play

  • HTTPS SSL encryption process for establishing a connection

The diagram below:

Process details:

  1. ① The browser of the client sends a request to the server, and transmits the version number of the SSL protocol of the client, the type of encryption algorithm, the generated random number, and other information needed for communication between the server and the client.
  2. ② The server sends the SSL protocol version number, encryption algorithm type, random number, and other related information to the client. At the same time, the server also sends its certificate to the client.
  3. (3) the client use the server to get the information of the legality of the authentication server, the server includes: the legality of certificate has expired, release the server certificate CA is reliable, the issuer public key certificate can correctly solve the server certificate of “digital signatures” issuers, domain name whether on the server’s certificate and match the actual domain name server. If the validity verification fails, the communication is disconnected. If the validity is verified, the fourth step will be continued.
  4. ④ The client randomly generates a symmetric password for communication, encrypts it with the public key of the server (obtained from the server certificate in Step 2), and sends the encrypted pre-master password to the server.
  5. ⑤ If the server requires the customer’s identity authentication (optional during the handshake), the user can create a random number and then sign the data, and send the random number containing the signature to the server together with the customer’s own certificate and the encrypted “pre-master password”.
  6. ⑥ If the server requires customer identity authentication, the server must verify the validity of the customer certificate and signature random number. The specific validity verification process includes: Check whether the customer’s certificate use date is valid, whether the CA that provides the certificate is reliable, whether the issuing CA’s public key can unlock the digital signature of the issuing CA, and whether the customer’s certificate is in the Certificate Repeal list (CRL). If the inspection does not pass, communication immediately interrupted; If verified, the server unlocks the encrypted “pre-master password” with its own private key, and then performs a series of steps to generate the master communication password (the client generates the same master communication password in the same way).
  7. ⑦ The server and client use the same master password that is “call password”, a symmetric key for SSL protocol encryption and decryption communication of secure data communication. At the same time, the integrity of data communication should be completed during SSL communication to prevent any changes in data communication.
  8. The client sends a message to the server, indicating the following steps of data communication. The master password in ⑦ is a symmetric key, and at the same time notifies the server client that the handshake process is over.
  9. ⑨ The server sends a message to the client indicating that the master password in step 7 is a symmetric key and notifies the client that the server-side handshake process is over.
  10. ⑩ After the SSL handshake is complete, data communication over the SSL secure channel starts. The client and server use the same symmetric key for data communication and check the communication integrity.

To resolve the problem of Moya HTTPS certificate trust, self-signed certificate trust, and request rejection, there are several ways:

  • Method 1: Disable HTTPS certificate authentication (not recommended).

This method is equivalent to turning HTTPS into HTTP, which is very insecure and vulnerable to data theft.

/// Disable HTTPS authentication

let serverTrustPolicies: [String: ServerTrustPolicy] = ["172.16.88.106": disableEvaluation]let manager = Manager(
configuration: URLSessionConfiguration.default,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)

let provider = MoyaProvider(manager: manager, plugins: [NetworkLoggerPlugin(verbose: true)])


Copy the code

The first approach is a workaround. It directly turns off Domain authentication for Https. Although the request can proceed normally, if a proxy is added between the client and server, and the proxy replaces the certificate when the request is sent, the proxy can easily obtain the requested data.

  • Method 2: Verify the self-signed certificate, CN is valid (recommended):

Use pinCertificates in the ServerTrustPolicy enumeration.

case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
Copy the code

The specific implementation code is as follows:

//
// JPNetworkProvider.swift
// JimuPro
//
// Created by yulu kong on 2019/10/24.
// Copyright © 2019 UBTech. All rights reserved.
//

import Moya
import RxSwift
import Alamofire

typealias FileNetworking = JPNetworkProvider<FileManagerAPI>

let APIFileManager = FileNetworking(plugins: [NetworkLoggerPlugin(verbose: true)], isHttps: true)

final class JPNetworkProvider<Target: TargetType> :MoyaProvider<Target> {
    
    init(plugins: [PluginType] = [LoadingPlugin()], isHttps:Bool = true) {
        let configuration = URLSessionConfiguration.default
        configuration.httpAdditionalHeaders = Manager.defaultHTTPHeaders
        configuration.timeoutIntervalForRequest = kTimeoutIntervalForRequest
        var manager = Manager(configuration: configuration)
        if isHttps {
            let policies: [String: ServerTrustPolicy] = [
                 getFileTransportIP(): .pinPublicKeys(publicKeys: ServerTrustPolicy.publicKeys(),
                 validateCertificateChain: false,
                 validateHost: true)]/ / debugPrint (JPNetworkProvider, HTTPS, : \ "(policies)")
            manager = Manager(configuration: configuration,serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies))
        }
        manager.startRequestsImmediately = false
        
        super.init(endpointClosure:JPNetworkProvider.endpointMapping ,manager: manager, plugins: plugins)
     }
    
    

        func requestWithProgress(
            _ target: Target,
            _ callbackQueue: DispatchQueue? = nil._ isCache: Bool = false,
            file: StaticString = #file,
            function: StaticString = #function,
            line: UInt = #line
            ) -> Observable<ProgressResponse> {

            return self.rx.requestWithProgress(target, callbackQueue: callbackQueue).do(onNext: { (progressResponse) in})}func request(
        _ target: Target,
        _ isCache: Bool = false,
        file: StaticString = #file,
        function: StaticString = #function,
        line: UInt = #line
        ) -> Single<Response> {
        let requestString = "\(target.method) \(target.path)"
        
        return self.rx.request(target)
            .filterSuccessfulStatusCodes()
            .do(onSuccess: { (value) in
                let message = "*** SUCCESS: \(requestString) (\(value.statusCode))"
                logger.debug(message, file: file, function: function, line: line)
                
            }, onError: {(error) in
            
                //NotificationCenter.post(customNotification: .netError)
                
                if let response = (error as? MoyaError)? .response {if let jsonObject = try? response.mapJSON(failsOnEmptyData: false) {
                let message = "*** FAILURE: \(requestString) (\(response.statusCode))\n\(jsonObject)"
                logger.warning(message, file: file, function: function, line: line)
                } else if let rawString = String(data: response.data, encoding: .utf8) {
                let message = "*** FAILURE: \(requestString) (\(response.statusCode))\n\(rawString)"
                logger.warning(message, file: file, function: function, line: line)
                } else {
                let message = "*** FAILURE: \(requestString) (\(response.statusCode))"
                logger.warning(message, file: file, function: function, line: line)
                }
                } else {
                let message = "*** FAILURE: \(requestString)\n\(error)"
                logger.warning(message, file: file, function: function, line: line)
                }
            }, onSubscribed: {
                let message = "*** REQUEST: \(requestString)"
                logger.debug(message, file: file, function: function, line: line)
            })
    }
    
    private static func endpointMapping<Target: TargetType>(target: Target) -> Endpoint {
    
        var param: [String:Any] = [:]
        switch target.task {
        case let .requestParameters(parameters, _):
            param = parameters
        default:break
        }
    
        var url = "\(target.baseURL)\(target.path)?"
        
        if target.method == .get {
            
            
            let s = param.map { (key,value) -> String in
                
                return "\(key)=\(value)&"
            }
            
            for p in s {
                url += p
            }
            
            url.remove(at: String.Index(encodedOffset: url.count - 1))
            
            / / logger. The info (" kyl request link: \ \ n request (url) methods: \ (target) method) ")
            
        }else{
            / / logger. The info (" kyl request link: \ \ n parameters (url) : \ \ n (param) request method: \ (target) method) ")
            
        }
    
        return MoyaProvider.defaultEndpointMapping(for: target)
    }
    
}



Copy the code

The authentication code is as follows:

//
// HTTPSManager.swift
// JimuPro
//
// Created by yulu kong on 2019/10/28.
// Copyright © 2019 UBTech. All rights reserved.
//

import UIKit
import Alamofire
import Kingfisher

class HTTPSManager: NSObject {
    
    // MARK: -sll certificate processing
   static func setKingfisherHTTPS(a) {
        // Fetch the downloader singleton
        let downloader = KingfisherManager.shared.downloader
        // Trust the Server IP
        downloader.trustedHosts = Set([getFileTransportIP()])
    }
    
   static func setAlamofireHttps(a) {
        
        SessionManager.default.delegate.sessionDidReceiveChallenge = { (session: URLSession, challenge: URLAuthenticationChallenge) in
            
            let method = challenge.protectionSpace.authenticationMethod
            if method == NSURLAuthenticationMethodServerTrust {
                // Verify the server, directly trust or verify the certificate of the two options, recommended to verify the certificate, more security
                return HTTPSManager.trustServerWithCer(challenge: challenge)
// return HTTPSManager.trustServer(challenge: challenge)
                
            } else if method == NSURLAuthenticationMethodClientCertificate {
                // Authenticate the client certificate
                return HTTPSManager.sendClientCer()
                
            } else {
                // In other cases, no validation
                return (.cancelAuthenticationChallenge, nil)}}}// Trust the server directly without doing any validation
    static private func trustServer(challenge: URLAuthenticationChallenge)- > (URLSession.AuthChallengeDisposition.URLCredential?). {let disposition = URLSession.AuthChallengeDisposition.useCredential
        let credential = URLCredential.init(trust: challenge.protectionSpace.serverTrust!)
        return (disposition, credential)
        
    }
    
    // Verify the server certificate
    static  func trustServerWithCer(challenge: URLAuthenticationChallenge)- > (URLSession.AuthChallengeDisposition.URLCredential?). {var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
        var credential: URLCredential?
        
        // Get the certificate sent by the server
        let serverTrust:SecTrust = challenge.protectionSpace.serverTrust!
        let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0)!
        let remoteCertificateData = CFBridgingRetain(SecCertificateCopyData(certificate))!
        
        // Load the local CA certificate
// let cerPath = Bundle.main.path(forResource: "oooo", ofType: "cer")!
// let cerUrl = URL(fileURLWithPath:cerPath)
        
        let cerUrl = Bundle.main.url(forResource: "server", withExtension: "cer")!
        let localCertificateData = try! Data(contentsOf: cerUrl)
        
        if (remoteCertificateData.isEqual(localCertificateData) == true) {
            // The server certificate is verified
            disposition = URLSession.AuthChallengeDisposition.useCredential
            credential = URLCredential(trust: serverTrust)
            
        } else {
            // Server certificate verification failed
            //disposition = URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge
            disposition = URLSession.AuthChallengeDisposition.useCredential
            credential = URLCredential(trust: serverTrust)
        }
        
        return (disposition, credential)
        
    }
    
    // Send the client certificate to the server for verification
    static  func sendClientCer(a)- > (URLSession.AuthChallengeDisposition.URLCredential?). {let disposition = URLSession.AuthChallengeDisposition.useCredential
        var credential: URLCredential?
        
        // Obtain the path of the P12 certificate file
        let path: String = Bundle.main.path(forResource: "clientp12", ofType: "p12")!
        let PKCS12Data = NSData(contentsOfFile:path)!
        let key : NSString = kSecImportExportPassphrase as NSString
        let options : NSDictionary = [key : "123456"] // Client certificate password
        
        var items: CFArray?
        let error = SecPKCS12Import(PKCS12Data, options, &items)
        
        if error == errSecSuccess {
            
            let itemArr = items! as Array
            let item = itemArr.first!
            
            let identityPointer = item["identity"];
            let secIdentityRef = identityPointer as! SecIdentity
            
            let chainPointer = item["chain"]
            let chainRef = chainPointer as? [Any]
            
            credential = URLCredential.init(identity: secIdentityRef, certificates: chainRef, persistence: URLCredential.Persistence.forSession)
        }
        
        return (disposition, credential)
        
    }
    
}


Copy the code

2.2.2 Data parsing library

2.2.2.1 ObjectMapper
  • Source code download: ObjectMapper

ObjectMapper is a data model transformation framework written by Swift language, we can easily convert model objects into JSON, or JSON generation corresponding model classes.

It has the following characteristics:

  1. Map JSON to objects
  2. Map objects to JSON
  3. Support for nested objects (used alone in arrays or dictionaries)
  4. Supports custom transformations during mapping
  5. Supporting structure
  6. Support the Immutable
  • Model class definition:

Creating model classes requires implementing the Mappable interface, including init? ObjectMapper (map: map) and func Mapping (map: map) use the <- special operator to represent the mapping between JSON and model attributes

Example code is as follows:

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?

// Validate JSON before object sequence number, return nil to prevent mapping from happening
    required init? (map: Map) {
      // Check whether the JSON has a name field
      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"]}}Copy the code
  • JSON string to model class:
let user = User(JSONString: JSONString)
/ / use the Mapper
let user = Mapper<User> ().map(JSONString: JSONString)
// Use Mapper to model array
let users: [User] = Mapper<User>().mapArray(JSONString: JSONString)
Copy the code
  • Model class to JSON string:
The prettyPrint argument is used to print readable JSON
let JSONString = user.toJSONString(prettyPrint: true)
/ / use the Mapper
let JSONString = Mapper().toJSONString(users, prettyPrint: true)
Copy the code
  • Supported types:
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 // The above optional type
Implicitly Unwrapped Optionals of the above // The above implicit resolution is optional

Copy the code
  • Simple mapping of nested objects:
  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"]}}Copy the code
  • Custom conversion:ObjectMapperProvides some type conversions such asDateTransform,DataTransform,HexColorTransform, but we need to customize what is not provided. The following examples are implementedNSURLTransform

When it comes to json parsing dictionaries into models, we may often use frameworks such as SwiftyJSON or HandyJSON. SwiftyJSON only converts JSON string into dictionary, we need to press key or corresponding value. This is flexible, but cumbersome, and is suitable for custom models that contain computational attributes. HandyJSON will automatically convert the JSON string to the model we defined by reflection, which needs to inherit HandyJSON as follows:

/// a Bluetooth command was received
struct BluetoothInfo: HandyJSON {
    
    var action: String?
    var content: String?//json serialized string
}
Copy the code

And ObjectMapper is protocol oriented programming, code without HandyJSON strong invasion, and solve SwiftyJSON pain points, should be combined with two kinds of advantages, in addition ObjectMapper can also define computational attributes, so that you can use the same model to provide the upper use, It is not necessary to transform the model several times.

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
    }

}
Copy the code

In addition, there is a relatively useful framework AlamofireObjectMapper:

This framework can be used in combination with Alamofire and ObjectMapper, and extends responseObject and responseArray methods for Alamofire’s Request class, which can more conveniently convert JSON data returned by network communication into objects

Here is the sample code:

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

    let weatherResponse = response.result.value

    if letthreeDayForecast = weatherResponse? .threeDayForecast {for forecast in threeDayForecast {
            print(forecast.day)
            print(forecast.temperature)           
        }
    }
}
Copy the code
2.2.2.2 Moya-ObjectMapper/Swift
  • Moya-objectmapper /Swift

Installation method:

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'
Copy the code
  • First create a model:
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"]}}Copy the code

Rxswift and Reactivesswift are available without:

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}})Copy the code

How to use 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)
Copy the code

How to use 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}}Copy the code

ReactiveSwift introduction:

Activeswift provides composable, declarative, and flexible primitives built around the grand concept of value streams over time. These primitives can be used to uniformly represent common Cocoa and generic programming patterns that are essentially observational behaviors, such as delegate patterns, callback closures, notifications, control operations, response chain events, and key-value observation (KVO). Because all these different mechanisms can be represented in the same way, it’s easy to group them together declaratively, bridging the gap with less spaghetti code and state.

2.2.3 Rxswift framework and related extensions

2.2.3.1 RxDataSources
  • Source: RxDataSources

  • RxDataSources features:
  1. O(N) algorithm for calculating differences: This algorithm assumes that all parts and terms are unique and therefore unambiguous. If there is ambiguity, the fallback automatically refreshes for non-animation.
  2. It applies additional heuristic methods, send a minimum number of orders to the sectional view: although running time is linear, but the number of choice for sending commands are usually much less than linear Best (probably) limit the number of changes in the smaller range, if the increase in the amount of change is linear, simply to normal to reload
  3. Support extension project and section structure: with IdentifiableType and Equatable to expand your project, with AnimatableSectionModelType expanding your part
  4. Section animation: Insert, delete, move item animation: Insert, delete, move, overload (if old value does not equal new value)
  5. Configurable animation types to insert, reload and delete (auto, fade out,…)
  6. Sample application
  7. Random Stress Test (sample APP)
  8. Support for out-of-the-box editing (sample application)
  9. Applies to both UITableView and UICollectionView

Installation:

CocoaPods

Podfile

pod 'RxDataSources'.'~ > 4.0'
Copy the code

Carthage

Cartfile

github "RxSwiftCommunity/RxDataSources"~ > 4.0Copy the code
  • Use:
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)
Copy the code
2.2.3.2 RxSwiftExt
  • Source code download:RxSwiftExt

If you are using Rxswift, you may encounter situations where the built-in operators do not provide the required functionality. To avoid bloating, the Rxswift kernel is designed to be as compact as possible. The purpose of this repository is to provide additional convenience operators and reactive extensions.

Installation: This branch of RxSwiftExt targets Swift 5. X and Rxswift 5.0.0 or higher.

If you are looking for Swift version 4 of RxSwiftExt, use version 3.4.0 of the framework.

CocoaPods

Add to your Podfile:

pod 'RxSwiftExt'.'~ > 5'
Copy the code

This will install both RxSwift and RxCocoa extensions. If you only want to install the RxSwift extension and not the RxCocoa extension, simply use:

pod 'RxSwiftExt/Core'
Copy the code

Using Swift 4:

pod 'RxSwiftExt'.'- > 3'
Copy the code

Carthage

github "RxSwiftCommunity/RxSwiftExt"
Copy the code

RxSwiftExt extends the following operations:

  • unwrap: Opens options and filters out null values.
Observable.of(1.2.nil.Int? (4))
    .unwrap()
    .subscribe { print($0)}Copy the code

Results:

next(1)
next(2)
next(4)
Copy the code
  • ignore: Ignores specific elements.
Observable.from(["One"."Two"."Three"])
    .ignore("Two")
    .subscribe { print($0)}Copy the code

Results:

next(One)
next(Three)
completed

Copy the code
  • ignoreWhen: Ignores elements based on closures.
 Observable<Int>
    .of(1.2.3.4.5.6)
    .ignoreWhen { $0 > 2 && $0 < 6 }
    .subscribe { print($0)}Copy the code

Results:

next(1)
next(2)
next(6)
completed
Copy the code
  • once: Sends the next element exactly once to the first subscriber that receives it. Further subscribers will get an empty sequence.
let obs = Observable.once("Hello world")
  print("First")
  obs.subscribe { print($0)}print("Second")
  obs.subscribe { print($0)}Copy the code

Results:

First
next(Hello world)
completed
Second
completed
Copy the code
  • distinct: Only pass elements if they never appear in the sequence.
Observable.of("a"."b"."a"."c"."b"."a"."d")
    .distinct()
    .subscribe { print($0)}Copy the code

Results:

next(a)
next(b)
next(c)
next(d)
completed
Copy the code
  • mapTo: replaces each element with the value provided.
Observable.of(1.2.3)
    .mapTo("Nope.")
    .subscribe { print($0)}Copy the code

Results:

next(Nope.)
next(Nope.)
next(Nope.)
completed
Copy the code
  • mapAt: converts each element to a value on the supplied key path.
struct Person {
    let name: String
}

Observable
    .of(
        Person(name: "Bart"),
        Person(name: "Lisa"),
        Person(name: "Maggie")
    )
    .mapAt(\.name)
    .subscribe { print($0)}Copy the code

Results:

next(Bart)
next(Lisa)
next(Maggie)
completed
Copy the code
  • not: Negative Boolean value.
Observable.just(false)
    .not()
    .subscribe { print($0)}Copy the code

Results:

next(true)
completed
Copy the code
  • and: Verifies that each emitted value is true
Observable.of(true.true)
	.and()
	.subscribe { print($0)}Observable.of(true.false)
	.and()
	.subscribe { print($0)}Observable<Bool>.empty()
	.and()
	.subscribe { print($0)}Copy the code

Results:

success(true)
success(false)
completed
Copy the code
  • cascade: Cascades through a series of observables, dropping the previous subscription as soon as an observable starts emitting elements further down the list.
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")
Copy the code

Results:

next(a:1)
next(a:2)
next(b:1)
next(c:1)
next(c:2)
Copy the code
  • pairwise: Groups the elements emitted by an observable into arrays, where each array consists of the last two consecutive items; It’s like a sliding window.
Observable.from([1.2.3.4.5.6])
    .pairwise()
    .subscribe { print($0)}Copy the code

Results:

next((1, 2))
next((2, 3))
next((3, 4))
next((4, 5))
next((5, 6))
completed
Copy the code
  • nwise: Groups the emitted elements of an observable into arrays, where each array consists of the last N consecutive items; It’s like a sliding window.
Observable.from([1.2.3.4.5.6])
    .nwise(3)
    .subscribe { print($0)}Copy the code

Results:

next([1, 2, 3])
next([2, 3, 4])
next([3, 4, 5])
next([4, 5, 6])
completed
Copy the code
  • retry: Repeats the sequence observed by the source with the given behavior until an error or successful termination occurs. There are four types of actions with different predicates and delay options :immediate, delayed, exponentialDelayed, and 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 Double
_ = 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)")})Copy the code

Results:

Receive event: First
Receive event: Second
Receive event: First
Receive event: Second
Receive event: First
Receive event: Second
Receive error: fatalError
Copy the code
  • repeatWithBehavior: When the source observation sequence is complete, it is repeated with the given behavior. This operator takes the same arguments as the retry operator. There are four types of actions with different predicates and delay options :immediate, delayed, exponentialDelayed, and 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 Double
_ = completingObservable.repeatWithBehavior(.exponentialDelayed(maxCount: 3, initial: 1.0, multiplier: 1.2), scheduler: delayScheduler)
    .subscribe(onNext: { event in
        print("Receive event: \(event)")})Copy the code

Results:

Receive event: First
Receive event: Second
Receive event: First
Receive event: Second
Receive event: First
Receive event: Second
Copy the code
  • catchErrorJustCompleteWhen an error occurs, cancel the error condition and complete a sequence
let _ = sampleObservable
    .do(onError: { print("Source observable emitted error \ [$0), ignoring it") })
    .catchErrorJustComplete()
    .subscribe {
        print ("\ [$0)")}Copy the code

Results:

next(First)
next(Second)
Source observable emitted error fatalError, ignoring it
completed
Copy the code
  • pausable: suspends the element of the source observation sequence unless the latest element from the second observation sequence is true.
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)}Copy the code

Results:

next(2)
next(3)
Copy the code
  • apply: Apply provides a unified mechanism for applying transformations on observable sequences without having to extend ObservableType or duplicate your transformations. See the discussion on Github for more reasons
// 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)
Copy the code
  • filterMap: A common pattern in Rx is to filter out some values and then map the rest to other values. FilterMap allows you to do this in one step:
// 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)}Copy the code

The sequence above remains even 2, 4, 6 and produces sequences 4, 8, 12.

  • errors, elements: These operators only apply to observable sequences materialized using the Materialize () operator (from RxSwift Core). Error returns a filtered sequence of error events, the elements thrown. Element returns a filtered sequence of element events, throwing an error.
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())
Copy the code
  • fromAsync: Converts a simple asynchronous completion handler into an observable sequence. Suitable for use with existing asynchronous services that invoke completion handlers with only one parameter. Emits the result generated by the completion handler, and then completes.
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)
Copy the code
  • zip(with:): Handy version of Observable.zip(_:). Merges the specified observable sequence into one observable sequence, using the selector function whenever all observable sequences produce an element at the corresponding index.
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)
    })
Copy the code

Results:

next("a1")
next("b2")
next("c3")
Copy the code
  • merge(with:)The convenience version of Observable.merge(_:). Combine elements from an observable sequence with elements from different observable sequences into one observable sequence.
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)
    })
Copy the code

Results:

1, 2, 1, 3, 4, 5, 5, 6, 6Copy the code
  • ofTypeThe ofType operator filters the element of the observable sequence if it is an instance of the provided type.
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)}Copy the code

Results:

next(2)
next(5)
completed
Copy the code
  • withUnretainedThe: Withunretain (_:resultSelector:) operator provides an unretained reference to an object that can be used safely (that is, not implicitly unwrapped), as well as the events emitted by the sequence. If the supplied object cannot be successfully retained, seqeunce completes
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()
Copy the code

Results:

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
Copy the code
  • count: The number of items emitted when an observable terminates with no errors. If given a predicate, only the elements that match the predicate are evaluated.
Observable.from([1.2.3.4.5.6]).count{$0 % 2= =0 }
    .subscribe()
Copy the code

Results:

next(3)
completed
Copy the code
  • partition: Divides a flow into two separate element flows that match or do not match the provided predicate.
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
Copy the code
  • bufferWithTrigger: Collects elements observable from the source and emits them as an array when the trigger emits.
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
Copy the code
2.2.3.3 NSObject+Rx
  • Source code download:NSObject+Rx

If you use Rxswift in general you will often need to let disposeBag = disposeBag () to define a garbage bag object that will be used to destroy the recycled sequence of resources. It is cumbersome to define such a thing in every class. NSObject+Rx makes this easier for you, you don’t need to define let disposeBag = disposeBag (), just ob.rx.disposebag, for example:

thing
  .bind(to: otherThing)
  .disposed(by: rx.disposeBag)
Copy the code
  • Installation method: CocoaPods

Add to your Podfile:

pod 'NSObject+Rx'
Copy the code

Carthage

Add to Cartfile:

github "RxSwiftCommunity/NSObject-Rx"
Copy the code
2.2.3.4 RxViewController
  • Source code download:RxViewController

RxViewController is the RxSwift wrapper for UIViewController and NSViewController.

With RxViewController wrapped, you can call the viewDidLoad method in VC like this:

self.rx.viewDidLoad
  .subscribe(onNext: {
    print("viewDidLoad 🎉")})Copy the code

In addition, RxViewController provides the following apis:

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>}Copy the code
2.2.3.5 RxGesture
  • RxGesture

RxGesture

view.rx
  .tapGesture()
  .when(.recognized)
  .subscribe(onNext: { _ in
    //react to taps
  })
  .disposed(by: stepBag)
Copy the code

You can also respond to multiple gestures. For example, you might want to turn off a photo preview when the user clicks or slides it up or down:

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

Rx. Gesture is defined as an Observable

where G is the actual type of the gesture recognizer so it emits the gesture recognizer itself (this is handy if you want to call methods such as asLocation(in view:) or asTranslation(in view:)).

RxGesture supports the following gesture:

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>
Copy the code

If you use a gesture recognizer alone, choose the viet.rx.foogesture () syntax over viet.rx.anygesture (.foo()), because it returns the concrete UIGestureRecognizer subclass and prevents you from converting it to SUBSCRIBE ().

  • RxGesture Filter: By default, the gesture recognizer’s state has no filter. This means that you will always receive the first event (almost always.possible) with the initial state of the gesture recognizer.

By default, the state of the gesture recognizer has no filter. This means that there are preferred states that can be used for various gestures (iOS and macOS) :

view.rx.tapGesture().when(.recognized)
view.rx.panGesture().when(.began, .changed, .ended)
Copy the code

If you are observing multiple gestures at the same time, you can use the when() operator if you want to filter the same state for all gesture recognizers, or use the tuple syntax for separate filtering:

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)
Copy the code

Here’s an example of an official demo app that includes all recognizers: ➡️ iOS, macOS.

  • RxGesture also supports delegate customization:

Every gesture recognizer has a default RxGestureRecognizerDelegate. It allows you to customize each delegate method using a policy:

  1. .always: Whether the corresponding delegate method returns true
  2. .never: returns false to the appropriate delegate method
  3. .custom: gets the associated closure to execute to return the value to the corresponding delegate method

Here are the available policies and their corresponding delegate methods:

beginPolicy                   -> gestureRecognizerShouldBegin(:_)
touchReceptionPolicy          -> gestureRecognizer(_:shouldReceive:)
selfFailureRequirementPolicy  -> gestureRecognizer(_:shouldBeRequiredToFailBy:)
otherFailureRequirementPolicy -> gestureRecognizer(_:shouldRequireFailureOf:)
simultaneousRecognitionPolicy -> gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)
eventRecognitionAttemptPolicy -> gestureRecognizer(_:shouldAttemptToRecognizeWith:) // macOS only
pressReceptionPolicy          -> gestureRecognizer(_:shouldReceive:) // iOS only
Copy the code

This delegate can be customized in the configuration package:

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}})Copy the code

Found in default values can be RxGestureRecognizerDelegate. Swift.

  • RxGesture also supports full customization:

You can also replace the default delegate with your own delegate, or delete it. The code is as follows:

view.rx.tapGesture { [unowned self] gestureRecognizer, delegate in
  gestureRecognizer.delegate = nil
  // or
  gestureRecognizer.delegate = self
}
Copy the code
  • Installation method: CocoaPods

Add this to Podfile

pod "RxGesture"
Copy the code

$ pod install Carthage

Add this to Cartfile

github "RxSwiftCommunity/RxGesture"~ > 3.0Copy the code

$ carthage update

2.2.3.6 RxOptional
  • Source code download:RxOptional

    RxOptionalApplicable to Swift option and “occupancy” typeRxSwiftExtension.

All operators are also available for drivers and signals unless otherwise noted.

  • Optional operation: filterNil
Observable<String? > .of("One".nil."Three")
    .filterNil()
    // Type is now Observable<String>
    .subscribe { print($0)}Copy the code

Results print:

next(One)
next(Three)
completed
Copy the code

replaceNilWith

Observable<String? > .of("One".nil."Three")
    .replaceNilWith("Two")
    // Type is now Observable<String>
    .subscribe { print($0)}Copy the code

Print result:

next(One)
next(Two)
next(Three)
completed
Copy the code

errorOnNil

Note: Not available on drivers because drivers cannot fail. By default, rxOptionalError. foundnilwhile EUNwrappingOptional has an error.

Observable<String? > .of("One", nil, "Three") .errorOnNil() // Type is now Observable<String> .subscribe { print($0) }Copy the code

Results print:

next(One)
error(Found nil while trying to unwrap type <Optional<String>>)
Copy the code

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)}Copy the code

Print result:

next(One)
next(A String from a new Observable)
next(Three)
completed
Copy the code

DistinctUntilChanged usage:

Observable<Int? > .of(5.6.6.nil.nil.3)
    .distinctUntilChanged()
    .subscribe { print($0)}Copy the code

Print result:

next(Optional(5))
next(Optional(6))
next(nil)
next(Optional(3))
completed
Copy the code
  • The main operations are:
  1. String
  2. Array
  3. Dictionary
  4. Set

Currently, it cannot be extended to conform to other protocols in Swift protocol. Currently, the type listed above conforms to Occupiable. You can also make custom types compliant to Occupiable.

How to use filterEmpty:

Observable"[String]>
    .of(["Single Element"], [], ["Two"."Elements"])
    .filterEmpty()
    .subscribe { print($0)}Copy the code

Print result:

next(["Single Element"])
next(["Two", "Elements"])
completed
Copy the code

ErrorOnEmpty:

Not available on the driver because the driver cannot fail. By default, RxOptionalError emptyOccupiable can appear error.

Observable"[String]>
    .of(["Single Element"], [], ["Two"."Elements"])
    .errorOnEmpty()
    .subscribe { print($0)}Copy the code

Print result:

next(["Single Element"])
error(Empty occupiable of type <Array<String>>)
Copy the code

catchOnEmpty

Observable"[String]>
    .of(["Single Element"], [], ["Two"."Elements"])
    .catchOnEmpty {
        return Observable"[String]>.just(["Not Empty"])
    }
    .subscribe { print($0)}Copy the code

Print result:

next(["Single Element"])
next(["Not Empty"])
next(["Two", "Elements"])
completed
Copy the code
  • Installation method:

CocoaPods

RxOptional is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'RxOptional'
Copy the code

Carthage

Add this to the Cartfile

github "RxSwiftCommunity/RxOptional"~ > 4.1.0Copy the code

$ carthage update

2.2.3.7 RxTheme
  • Source code download:RxTheme

RxTheme Extension framework for theme management based on Rx

  • Installation method: Cocoapods
pod 'RxTheme'.'~ > 4.0'
Copy the code

Carthage

github "RxSwiftCommunity/RxTheme"~ > 4.0.0Copy the code

With RxTheme you can define your app’s theme service like this:

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(a)case .dark:
            return DarkTheme()}}}let themeService = ThemeType.service(initial: .light)
Copy the code
  • Apply themes to the 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)
Copy the code

All streams generated by ThemeService are shared (1)

  • You can easily implement skin changing and theme switching functions with just one line of code:
themeService.switch(.dark)
// When this is triggered by some signal, you can use:
someSignal.bind(to: themeService.switcher)
Copy the code

In addition, RxTheme provides the following apis:

// Current theme type
themeService.type
// Current theme attributes
themeService.attrs
// Theme type stream
themeService.typeStream
// Theme attributes stream
themeService.attrsStream
Copy the code
  • The default binders already implemented are: 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

  • You can also choose to extend bindings in the code base yourself: becauseRxThemeUse fromRxCocoatheBinder<T>, soRxCocoaAny of those defined inBinderAll available here. This also makes the library super easy to extend in your code base. Here’s an example:
extension Reactive where Base: UIView {
    var borderColor: Binder<UIColor? > {return Binder(self.base) { view, color inview.layer.borderColor = color? .cgColor } } }Copy the code

Theme if you also want to use sugar view.theme. Boundary color, you must write another extension:

extension ThemeProxy whereBase: 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")}}}Copy the code
2.2.3.8 RxAnimated
  • Source code: RxAnimated

2.2.4 Image processing library

2.2.4.1 Kingfisher

2.2.5 Resource file management library

2.2.5.1 R.swift
  • Download the source code: R.swift
2.2.5.2 SwiftLint
  • SwiftLint

2.2.6 Key Management library

2.2.6.1 KeychainAccess
  • KeychainAccess

2.2.7 Automatic layout library

2.2.7.1 SnapKit
  • Source: SnapKit

2.2.8 UI libraries

2.2.8.1 NVActivityIndicatorView
  • Source: NVActivityIndicatorView
2.2.8.2 ImageSlidershow/Kingfisher
  • The source code download: ImageSlidershow/Kingfisher
2.2.8.3 DZNEmptyDataSet
  • DZNEmptyDataSet
2.2.8.4 Hero
  • Source code download: Hero
2.2.8.5 Localize-Swift
  • Source code: Localize-Swift
2.2.8.6 RAMAnimatedTabBarController
  • The source code download: RAMAnimatedTabBarController
2.2.8.7 AcknowList
  • Source: AcknowList
2.2.8.8 KafkaRefresh
  • Source: KafkaRefresh
2.2.8.9 WhatsNewKit
  • Download the source: WhatsNewKit
2.2.8.10 Highlightr
  • Highlightr
2.2.8.11 DropDown
  • DropDown
2.2.8.12 Toast-Swift
  • Download the source code: toasts-Swift
2.2.8.13 HMSegmentedControl
  • HMSegmentedControl
2.2.8.14 FloatingPanel
  • Source code: FloatingPanel
2.2.8.15 MessageKit
  • MessageKit
2.2.8.16 MultiProgressView
  • MultiProgressView
2.2.8.17 IQKeyboardManagerSwift
  • IQKeyboardManagerSwift

2.2.9 Log Management Library

2.2.9.1 CocoaLumberjack/Swift
  • Download source code: CocoaLumberjack/Swift

2.2.10 Data burying point database

2.2.10.1 Umbrella
  • Source code: Umbrella
2.2.10.2 Umbrella/Mixpanel
2.2.10.3 Umbrella/Firebase
2.2.10.4 Mixpanel
  • Source code download: Mixpanel
2.2.10.5 Firebace/Analytics

2.2.11 Advertising tool point library

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

2.2.12 Performance optimization related libraries

2.2.12.1 Fabric
2.2.12.2 Crashlytics

2.2.13 Other Tool Libraries

2.2.13.1 FLEX
  • Source code download: FLEX
2.2.13.2 SwifterSwift
  • Source code download: SwifterSwift
2.2.13.3 BonMot
  • Source code download: BonMot
2.2.13.4 DateToolsSwift
  • Download the source: DateToolsSwift
2.2.13.5 SwiftDate
  • SwiftDate SwiftDate

3. Architecture analysis of SwiftHub project

Reference: www.jianshu.com/p/fb63ca356…