开源项目分析(SwiftHub)Rxswift + MVVM + Moya 架构分析(一)第三方框架使用 (上)
开源项目分析(SwiftHub)Rxswift + MVVM + M; m =oya 架构分析(一)第三方框架使用0 Z # @ q x ? ; (下)
开源项目分析(SwiftHub)Rxswift + MVVM + Moya 架构分析(一)第三方框架使用
1. SwiftHub项目简介
SwiftHub 是大神Y o j 1 e X U NKhoren Markosyan 写的一个完全采用Rxswift + MVVM + Moya 的架构的项目,代码很精简,想学习MVVM架构的认真去研究这个项目的% & ( G f设计,对& l 1 L 你以后的编程思想和习惯都会有很大的帮助。(点击这7 o * K里下载:SwiftHub源码 )
1.1 SwiftHub项目UI
1.2 SwiftHub项目代码结构
2. Sl & Z D ) Y 4 g @wiftHub项目编译,用到的第三方库简& I [ +介
2w , Z , ) i M.1 SwiftHub项目编译
- 点击这里下载:SwiftHub源码
下载源码后,进入SwiftHub-master主目录,先要下载安装第三方库,如果你cd SwiftHub-master/
就: 8 _ 8 – + y ;直接执行pod iT z : c p rnstall
的话一般都会报错:
分析报错原因不难看出,已经提示我们需要先pod repo update
一下更新你本地的cocos pod库。
可能有的小伙伴网速不太好,pod install一直更新不了D y = b h z,这里提供了一份我编译好的源码:链接:pan.baidu.com/s/1qwkjY_Zr… 密码:60t7
2.2 Swif* E y $tHub项目用到的第三方框架
-
我只能惊叹,哇塞,怎么用了_ R a这么多第三方框架啊,我个人观点是不太主张用太多第三@ ~ w方框架,能自己实现都自己实现,除A 9 ) d , a @非要实现的功能必须要用第e Z Z 2 o X 0 T x三方框架。因为第三方框架会大大增( e b | u加我们ipa包的大小,对于v M * h nipa大小有要求的是个灾难,例如之前我们有一个项目使用
Realm
作为DB框架,但是发现这个框架实在是太占内存了足足有将近90MB,而我只是想里面一个小小的数据库存储相关的代码,后面改成WCDB.swift
框架,这个框架只有2MB左右。 -
下面我们先来看一下SwiftHub 项目用到的第三方框架吧:
# Uncomment the next line to define a global platform for your project
platform :ios, '11.0'
use_frameworks!
inE + t J q 4 2 Ohibit_all_warnings!
targm a L 7 7et 'SwiftHub' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
# Pods for Swift3 _ q U * U Q 8Hub
# Networking
pod 'Moya/RxSwift', '14.0.0-beta.2' # https://github.com/Moya/r O , ~ =Moya
pod 'Apollo', '0.19.0' # https://github.com/apollographql/apollo-ios
# RxP b L - M Extensions
pod 'RxDataSources', '~> 4.0' # https://github.com/RxSwiftCommunity/RxDataSources
pod 'RxSwiftE^ { H C Kxt', '~> 5.0' # https://github.com/RxSwiftCommunity/RxSwiftExt
pZ r j : f 7 c .od 'NSObject+Rx', '~> 5.0' # httpsG , S://github.com/RxSwif1 B = m 8 s ,tCommunity/NSObject-Rx
pod 'RxViewCs 6 h ] 8 Hontroller', '~> 1.0' # httpsB _ J ~ ( E 9 z 6://github.com/devxoul/RxViewController
pod 'RxGesture'& + ] d ` u O l, '~> 3.0' # https://github.c` % R 7 ! z com/RxSwh 3 3 JiftComt B U 0 r ! dmunity/RxGesture
pod 'RxOptional', '~> 4.0' # https:/i z 3 k 0 m L/github.com/RxSwiftCommunityx ` d # Z/RxOptional
pod 'RxTheme', '~> 4.0' # https://github.com/RxSwiftCommunity/RxTheme
#pod 'RxAnimated', '~> 0.4' # https://github.com/RxSwiftCommunity/RxAniV + j / n omated
# JSON Mapping
#pod 'ObjectMapper', :git => 'https://github.com/kajensen/ObjectMapper.git' # https://github.com/Hearst-DD/ObjectMapper
pod 'Moya-ObjectMapper/RxSwift2 ` ) } r ,', :git => 'https://github.com/khoren93/Moya-Obb j LjectMapper.git', :branch => '( X u h t Kmoya| t ( A 2 114' # https://github.com/i= @ ^ e svanbrA 6 C Suel/Moya-ObjectMappeH L H or
# Image
pod 'Kingfisher', '~> 5.0' # https://github.com/onevcat/KingfiA p ( C n B 4 L +sher
# Dat# 2 W : t {e
pod 'DateToolsSwift', '~> 4.0' # https://github.com/MatthewYork/DateToolsr V i = P p U
pod 'SwiftDate', '~> 6.0' # https://github.com/malcommac/SwiftDate
# Tools
pod 'R.swift', '~> 5.0' # https://github.com/mh 1 u mac-cain13/R.1 C @ Y W h Rswift
pod 'SwiftLr O x b 8 J Xint', '0.37.0' # https://github.com/reaE X u X E ` blm/SwiftLint
# Keychain
pod 'Key@ c K m #chainAccess', '~> 4.0' # https:n } D l M p I s f//github.com/kishikawakatsumi/KeychainAccess
# Fabric
pod 'Fabric'
pod 'Crashlytics'
# UI
pod 'NVActivitW 4 g x ;yIndicat| I l l / & ~ $ ^orView', '~> 4.0' # https://github.com/ninjaprox/NVAct{ 6 . I 5ivityIndicatorView
pod 'ImageSlideshow/Kingfisher', '~> 1.8' # https://github.com/zvonz l ! = } c Z J ;icek/ImageSE , # ! : Llideshow
pod 'DZNEmptyDataSet', '~&gu ^ ft; 1.0' # https://githO # & ? Q 2 ( mub.com/dzenbot/DZNEmptyDataSet
pod 'Hero', '~> 1.5.0' # https://github.com/lkzhao/Hero
pod 'Localize p c-Swift', '~> 3.0' # https://github.L $ W :com/marmel% n F 4 Uroy/Localize-Swift
pod 'RAMAnimatedTabBarController', '~>W X 9 5.0' # https://github.com/Ramotion/animated-tab-bg i _ L 2ar
pod 'AcknowList', '~> 1.8' # http* o D 1 ns://github.com/vtourraine/AcknowList
pod 'KafkaRefresh', '~> 1.0' # https) u ` F://githuk a L o @ X ? { xb.com/OpenFeyn/KafB _ ? } @ / ~ ekaRefresh
pod 'Wha% Y E ^tsNewKit', '~> 1.0' # https://github.com/SvenTiS . , Q ;igi/WhatsNewKit
pod 'Highlightr', '~> 2.0' # https://github.com/rasp/ J B X f G . 4u/Highlightr
po S N ]d 'DropDoZ ? m ( k ` ]wn', '~> 2.0' # https:/e j 5 ` J ,/github.cl X ^om/AssistH 3 V G ioLab/DropDown
pod 'Toast-Swi# K qft', '~>1 Q 4 5.0' # https://github.com/scalessec/Toast-Swift
pod 'HMSee P u hgmentedControl', '~> 1.0' # https://github.com/HeshamE R k L / ? NMegid/HMSegmentedControl
pod 'FloatingPanel', '~> 1.0' # https://github.com/SCENEE/FloatingPh @ i u Manel
pod 'MessageKit', '~> 3.0' # https://github.com/Message/ ^ # - G S kKit/MessageKit
pod 'MultiProgressView', '~> 1.0' # https://github.com/mac-gt y B allagher/MultiProgressView
# Keyboard
pod 'IQKeyboardManagerSwift', '~D # 2> 6.0' # https://github.com/hackiftekhar/IQKeyboardManager
# Auto LaZ w U $ C F H Myout
pl 6 ) 1 Y ` vod 'SnapKit', '~&* ! W # W 3 [ U :gt; 5.0' # https://github.com/SnapKit/SnapKit
# Code Quality
pod 'FLEX', :git => 'https://github.com/khoren9t M ` W w t E3/FLEX.git', :branch => 'remove_private_api' # https://github.com/Flipbo- + ` 4 ( Sard/FLEXn w I
pod 'SwifterSwift', '~> 5.0' # https://github.com/SwifterSwift/SwifterSwift
pod 'BonMot'3 * c [ E q, '~> 5.0' # https://! v - j p 3 9 C Ugithub.com/Rightpoint/BoT @ ; x 5 / 5nMot
# Logging
pod 'CocoaLumberjO k 6 _ { Q j 1 (ack/Swift', '~> 3] 9 Y l (.0' # https://github.com/W m 0 9 V ~ ZCocoaLumberjack/CocoaLumberjack
# Analyticsw ] ( / . { 9
# httpI j 1 9 O E Hs://github.com/devxou2 % ; D n S il/Umbre= [ ? j p 1 D k nlla
pod 'Umbrella/Mixpanel', '~> 0.8'
pod 'Umbrella/Firebh d L q aase'
pod 'Mixpanel', '~> 3.0' # httk x # 9 j 5 cps:/z n 7 f K/github.com/@ s d 5 X p ?mixpaneR 3 yl/mixpanel-iphone
pod 'Firebase/Analytics'U ? [ 7 C
# Ads
pods E @ w 'Firebase/AdMob'
pod 'Google-Mobile-Ads-SDK', '7.52.0'
target 'SwiftHubTests' do
inherit! :search_q . L + 2 a / 8 ^paths
# Pods f# | b Q l ~ 0 1 8or testing
pod 'Quick', '~> 2.0' # https://F A Z ) o qgithub.com/Quick/Quick
pod 'Nimble', '~> 8.0' # https://github.com/Quick/Nimble
#pod 'RxNimble', '~> 4.0' # https://github.a = 3 Mcom/RxSwiftCommunity/RxNimble
pod 'RxAtom ] @ o m : N Gic', :modular_headers =T y e _ @ ` ?> true
pod 'RxBlocking'c # 6 e # hq 0 ` 6 d nttps} s O z L V D://github.com/ReactiveX/RxSwift
pod 'Firebase'
end
end
target 'Sd # *wiftHubUITests' do
inherit! :seau ~ L z Orch_paths
# Pods for testing! B v { i w p K
end
post_iv e c T rnstall do |instalt Q 2 i 4 z d Mler|
# Cocou ~ D h h M Qapods 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 = Fn h ; E W i ;ile.join(folder, flag_name)
content = File.read(script)
content.gsub!(/set -e/,z m v u Q 0 "set -enKGB 2 P 7 @_FILo E ,E="#{file}6 Y - ; Z K = @"nifE A Z ; - w # - 9 [ -f . 3 ( 5 w K T"$KG_q T ; = 2 / )FILE" ]; then ew k qxit 0; finmkdir -p "#{folder}"ntouch "$KG_FILE"")
File.write(script, content)
end
# Enable tracing resources
insta? 1 { Zller.pods_pr/ n Q 7 i e n goject.targets.each do |target|
if target.name == 'RxSwift'
target.build9 . t C M S_configurations.each do |con~ f I P sfig|
if config.name == 'Debug'
config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['-D',B 3 q ~ g % I 'TRACE_RESOURCES']
end
end
end
end
end
- 接q . t y y下来,我们来分析这些第三方库都是用来干什么,说不定哪天你的项目也可以用到呢。
2.2.1 网络库
2.2.1.1 Alamofire
Alamofire和AFNetwork 是一对兄弟,是出自同一个公司的产品, 它是一个很好的Swift编写的J F v m b c网络框架库,提供了HTTP相关Z y N接口,能轻松实现c @ k L链式请求和响应,能实现文件上传,下载,断点续传,后台下载等功能o _ C g z j 5。
安装方式:
- CocoI j * M K t & WaPods安装:
pod 'Al0 v x j k r _amofire', '~> 5.1'
- Cr ^ X – F B 9arthage 安装:
github "Ak [ U : 8 Wlamofire/Alamofire" ~> 5.1
安装环境要求:
iOf n Y Q f E A G mS 10.0+ / macOS 10.12+ / t. 1 ? XvOS 10.0+ / watchOS 3.0+
Xcode 11+
Swift 5.1+
提供的功能特性:
- 可链请求/响应方法
- U| c = S BRL / JSON参数编码
- 上传文件/数据/流/ MultipartFormData
- 使用请求或简历数据下载文件
- 身份验证与URLCredential
- HTTP响应验证
- 上传和下载进度闭c x e P包与进度
- cURL命令输出
- 动态调整和重试请求
- TLS证书6 | v , # p m和公钥固定i # G N @
- 网络可达性
- 全面的单元和集成测试覆盖
- 完整的文档
为了让 Alam( s W ? y Lofire更专注于处理网络相关的事情,Alamofire软件基金会已经创建了额外的组件库来为Alamofire生态系统带来额外的功能。如:AlamofireImage库和 Alf q D ~ G l #amofireNetworkActivityIndicator
- AlamofireImage: 一个图像库,包括图像响应序列化器X ( Y – D,UII= = { g Z 2mage和UIImageView扩展,自定义图像过滤器,一个自动清除内存i ; 0 v B K缓存和一个基于优先级的图像下载c u u i Z a系统。
- AlamofireNetworkActivityIndicatT A E x i q Nor : 使用Alamofire控制iOS上的网络活动指示器的可见性。它包含可配置的延迟计时器! = S N z $ @ x Z,以帮助减少闪烁,并支持Q A Z 3 l不受Alamofire管理的URLSession实例。
-
Alamofire框架结构图:
-
关于Alamofire的使用可以参考我的一些博客:
Alamofire学习(一)网络基础
Al) 8 f 5amofire(二)URLSession
Alamofire(三)后台下载原理
- 网络请求步骤:
- 设置请求url
- 设置URLRequest对象,配置请求相关信息
- 创建会话配置URLSessionConfiguration
- 创建会话URLSession
- 创建任务和设置请求回调,并发起请求
简单请求代码:
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:/ N Q ( E , m L(json)")
let dict = json as! Dictionary<String, Any&x d 8 r fgt} P ; 4 i;
let list = dict["data"] as! Arx = Q z q Iray<AnyObject>
guard let result = [UserModel1].de# F xserialize(from: lisy . ; = 9 i 3 A t) else{return}
self.observable.onNext(result as [Any])
break
case .failure(let error):
print("errorh 6 M H F t h:(error)")
break
}
}
}
其中URLSessionConfiguration提供了框架的相关配置:
主要提供了以下3中方! C m P | E S式:
defay = L ( - o fult
:默认模式,常用模式,在该模式下系a , &统会创建持久化缓存,并在用户的钥匙串中保存证书ephemeral
:不支持持久性存储,所有内容的会随着session的生命周L V – U 7 } t期结束而释放
backT ( h Tground
:与default模L b 4 b Q式类似,在该模式下会创建一个独立线程来传输网络请求数据,可以在后台乃至APP关闭的时候也可以进行数据传输
- 创建会话:
let configuration = URLSessionConfiguratioP d (n.background(withIdentifier: "request_id")
let session = URLSession.init(configuration: configuration, delegate: self, de~ * % B B n G N YlegateQueue: OperationQueue.main)
session.dataTask(with: request) { (data, response, error) in
do {
let list = t5 r d ? Y f Q Mry JSONSerializationT S , V.jsonObject(with: data!, options: .allowFragments)
print(list)
}catch{
print(error)
}
}.resume()
此外还提供了很多属性来按需配置
- 常规属性:
identifier
:配置对象的后台会话标识符httpAdditionalHeaders
:与请求一起发送的附加头文件字典networkSer1 - BviceType
:网络服务的类型allowsCello v n 2ularAccess
:一个布尔值,用于是否应通过蜂窝网络进行连接timeoutIntervalFo9 Q . G t k -rRequ7 ? a P b g | &est
:等待附加数据的超时时间timeoutInterv/ 3 3 e }alM M X = 5ForResource
:资源请求允许的最大时间范围sharedContainerIdentifier
:应将后台URL会话中的文件下载到的共享容器的标识符waitsForConnectiv4 ! J S u U bity
:一个布尔值,指示会话是否应等待连接变为可用还是立即失败
- 设置Cookie策略:
httpCookieAcceptPolicy
:决定何时接受cookie的策略常量httpShouldSetCookies
:一个布尔值,确定请求是否包含来自cookie存储区的cookiehttpCookieStorage
:用于会话中存储cookie的cookie存储区HTTPCookie
:该对象为不* % m可变对象,从包含cookie属性的字典初始化,支持两个不同的cookie版本,( Q H Sv0、v1
- 设置安全策略:
TLS协议
:用于在两个通信应用程序之间提供保密性和数据完整S W 6 X : 2 n ? O性tlsMaximumSupportedProtocol
:在此会话中建立连接时客户端应请求的最大TLS协议版本tlsMinimumSupportedProtocol
:协议协商期% w & q #间应接受的最小TLS协议urlCredeE I E 9 $ 9 ( % RntialStorage
:为身份验证提供凭据的凭据存储区
- 设置缓存策略:
urlCache
:用于为会话中的请求提供缓存响应的URL缓存requestCachePolicy
:决定何时从缓存中返回响应的预定义常量
- 支持后台V 6 k . 8 v B模式:
sessionSendsLaunchEven| - qts
:一个( ! o r 1布尔值,指示当传输完成时( H * [ Y / / e +,应用程序应在后台恢复还是启动isDiscretionary
:一个布尔值,用于确定后台任务是否可以由系统自行安排已获得最佳性能shouldUseExtendedBackgroundIdleMode
:一个布尔值,指示当应用程序转移到后台时是否应保持TCP连接打开
- 支持自定义协议
protocolClasses
:在会话中处理请求的额外协议W , r q子类的数组URLProtoco^ S ; n 1l
:该对象用2 % { / t X $ W来处理加载协议特定URL数据
- 支持多路径TCP:
multipathServiW % x c 9 n ~ q lceType
:指A % a v I P 3定用于通过Wi-Fi和蜂窝接口传输数据的多路径TCP连接策略的服务类型
- 设置HTTP策略和代理属性:
httpMaximumConnectionsPerHost
:同时连接到给定主机的最大数量httpShouldUsePipelining
:) b l H b Z一个布尔值,m j & 4用于确定会话是否使用HTTP流水线connectionProxyDictionary
:包含相关要在此; A w H % A d )会话中使用的代理信息的字典
- 支持连接更改:
waitsForConnectivityb m - L 6 4 3 )
:一个布尔值,指示会话应等待连接可用还是立即失败
此外这里列举一下一些简单基本功能的用法:
- 链式响应
Alamofire.request("https://www.baidu.com").responseString { (response) in
print("Response String: (String(describV e i iing: response.result.value))"N K Y + q * |)
}.responseJSON { (response) in
pr/ 6 2 i T k hint("Response JSON:(String(describing: response.result.P C ~ ! Q i N Jvalue)")
}
- 添加
Headers
和parameter
let parameters: Pag N * hrameters = [
"foo": "bar",
"baz": ["a", 1],
"qux": [
"x": 1,
"y": 2,
"z": 3
]
]
let headers: HT( A t & r @ TPHeaders = [
"Accept" : "applicat$ A # ? D k U kion/json",
"Authorization" : "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="h o b a b = Y
]
Alamofire.request("https4 q F 9 B://www.baid.com/post", method: .post,+ u = / i a j J paraK a / V 7 r B ~meters: parameters, enf Z 4 q | - Kcoding: URLEncoding.httpBody, headers: headers)
- 状态码过滤
Alamofire.request("https://www.baid.com/get")
.validate(statusCode: 200..<300)
.validate(contentType: ["application/js n . v S 3 J ]son"]4 4 c + $)
.responseData { (response) in
switch response.result {
case .success:
print("successful")
case .failure:
print("errorB _ n A m 5 .")
}
- 上传文件
leW = d . Y ) = Bt fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")!
Alaw O P f W l , umofire.upload(fileURL, to: "https://www.baid.O . y } & 9 T 8com/post")
.uploadProgress { progress in // mc C ] g : i Q l _ain queue by default
print("Upload Progress: (progress.fractionComp7 e g C 7leted)"; [ N | 1)
}
.downloadProgress { progress in /| / 5/W K ( q O Z V 3 main queue by default
print("Download Progress: (progress.fractionCompleted)")
}
.responseJSON { response in
debugPrint(response)
}
- 上传Data数据
let imageData = UIImagePNGRepresentation(image)!
Alamofire.upload(imageData, to: "https://www.baid.{ X 5 m ~ 9 i (com/post").responseJSON { respB & 2 n 7 xonse in
debugPrint(response)
}
- 文件下载
Alamofire- s { Q ; # w.download("http @ K } + m k |s://www.baid.com/i{ D X %mage/png")
.downloadProgrs K t a % { q 3ess { progress in
print("Download Progress: (progresz 0 / O 7 0s.fractionCo! s n j 7 hmpleted)")
}
.responseData { response in
if let data = response.result.value {
let image = UIImage(data:$ ( K * 4 F data)
}
}
2.2.1.2d U b 0 RxswiftD W & { ~ + .
Rxswift家族提供非常好的函数响应式编程框架,使用Rxswift编写代码可以让代码变得非常简洁,逻辑清晰,如果配合Moya + Rxswift + MVVM架构,真的是很完美,这个开源项目Sw7 { / G T i aiftHub就是这样的一个完美的项目。
ReactiveX(简写:Rx)是一个可以帮助我们简化异步编程的框架。而 RxSwifN W a , & ` U @t 是 Rx 的 Swift 版本。除了 RxSwift,还有 RxJava、RxJS、Rx.Net 等,对应的OC 版本则是 RAC(Re4 / F t F , ^ ^activeCocoa),这里是 RxSwift 的 github 地址 ,已经有了将近 18.2K 颗星了。
RxSwiE X B g gft
: RxSwift的核心,提供由ReactiveX(主要)定义的Rx标准。它w R ]没有其他依赖项。
RxCocoa
: 为一般的iOS/macOd b g CS/watchOS & tvOS应用程序开发提供特定于cocoa的功能,如绑定、特L : i性等Z T ! 5。它同时依赖于RxSwift和RxRelay。
RxRelay
: 提供发布中继和行为中继,这两个简单的主题包装器。这取决于RxSwift。
RxTesth S J 1 and RxBlocking
: 为基于rx的系统提供测试功能。这取决于RxSwift。
- 关于_ a } = . = l 4 MRxswift的使用可以参考我的一些博客:
Rxswift(一)函数响应式编程思想
RxSwift (二)序列核心逻辑分析
RxSwift (三)Observable9 [ H h | H y的创建,订阅,销毁
RxSwift(四)高阶函数
RxSwift(五)(Rxswift对比swift,oc用法)
Rxswift (六)销毁者Dispi t @ d # @ose源码分析
RxSwift(七)Rxswift对: 9 : j比swift用法
RxSwift (十) 基础使用篇 1- 序列,订阅,销毁
RxSwift学习之十二 (基础使用篇 3- UI控件扩展)
Rxswift一些简单使用如下:
- button点击事件:
//MARK: - RxSwift应用-button响应
func setupButton() {
// 传统UI事件
self.button.addTarget(self, action:C : W L #selector(didClickButton), for: .touchUpInside)
// 这样的操作 - 不行啊!代码逻辑与u k Q q G 7 E E事件逻辑分层
self.button.rx.tap
.subscribe(onNext: { [weak self] in
print("点了,小鸡炖蘑菇")b A I 2 B B r U
self?.view.backgroundColor = UIColor.orange
})
.disposed(by: diS [ #sposeBag)
}
- textfileda f # C # . 7 f文本响应
//MARK: - RxSwift应用-textfiled
func setupTextFiled() {
// 我们如果要对输入的文本进行操作 - 比如输入的的内容 然后我们获取里面的偶数
// self.textFiled.delegate = self
// 感觉是不是特别恶心
// 下面我们来看看Rx
self._ } O 9 q l g 4textFiled.rx.text.orEmpty.changed.subscribe(onNex2 P T n 7t: { (text) in
print("监听到了 - (text)")
}).disposed(by: disposeBag)
self.textFt g % ^ V K 7 Iiled.rx.text.bind(to: self.button.rx.title()).disposed(by: disposeBac Y v X 9 g)
}
- scrollView使用
//MARK: - Rxj t / OSwift应用-scrollView
func setupScrollerView() {
scrollView.rx.contentOffset.subscribe(onNext: { [weak self] (content) in
self?.view.backC O ! c x F OgroundColor = UIColor.init(red6 Q e ? x V: conG # _ ^tent.y/25N ^ , m A - U5.0*0.8, green: content.y/255.0*0.3, blue: content.y/255.0*0.6,c ) _ N o t I i V alpha: 1);
print(content.y)
}).dispom 0 Ksed(by: disposeBag)
}
- KVO
//MARK: - RxS9 3 k U k z X h Lwift应用-KVO
func setupKVO() {
// 系统KVO 还是比较麻烦D M B m L f _的
// person.addObserver(self, forKeyPath: "name", options: .new, context: nil)
pe3 Q N 8rsP & -on.rx.observeWeakly(String.self, "nam2 = } ^e").subscribe(onNext:] N Z _ b , T # { (c^ M B /hange) in
print(change ?? "hel` # &loword")
}).disposed(by: disposeBag)
}
- 通知
//MARK: - 通知
func setupNotification(){
NotificationCenter.default.rx
.notificati0 8 % f | xon(UIResponder.k$ C b g [ h beyboardWillShowNotification)
.subscribe { (event) inY { v 1 V
print(event)
}.disposed(by: disposeBag)
}
- 手势
//MARK: - 手势
func setupGe{ I { CstureRecognizer(){
let tap = UITapGestureRecognizer()
se+ } [ w = l * Flf.label$ ! U ] . T }.addGestureRecognizer(tap)
self.label.isUserInteractionEnabled = true
tap@ p $ % = | O.rx.event.subscribe { (event) in
print("点了label")
}.disposed(by: disposeBag)
}
- 网络请求
//MARK: - RxSwift应用-网络请求
func sC 1 T 1 Z V /eG q 2 9 R T 9 / VtupNextwork() {
let ur4 8 f 9 + g 3 ` :l = URLC - T S {(string: "https://www.baidu.com")
URLSession.sh* Q e v S :ared.rx.response(request: URLRequest(url: url!))
.subscribe(S ] f J X tonNe 2 Y r 3xt: { (rd $ [esponse, d. l y j N d W U %ata) in
print("response ==== (response)")
print("data ===== (data)")
}, onErr4 [ # ? j s + o @or: { (error) in
print("error ===== (error)")
}).disposed(by: disposeBag)
}
- 定时器
//MARK: - RxSwift应用-timer定时器
func setup# ] Z y b j &Timer() {. ) d # c d
timer = Observabley U y t @ _ W a X<Int>.interval(1, scheduler: MainScheduler.instance)
timer.subscribe(onNext, j R z W ^: { (n q *num) in
print("hello word (num)")X i 1 Z 2 p
}).disposed(by: disposeBag)
}
2.2.1.3 Moya
- 源A r ` [码下载:Moya
Moya是一个网络抽象层,它在底层将Alamj K 1 t ~ ! oofire进行封装,对外提供更简洁的接口供开发者调用。在Objective-C中,大部分开发者会使用AFNetwork进行网络请求,当业务复杂一些时,会对AFNetwork进行二次封装,编{ U } / :写一个适用于自己项目的网络抽象层。在D x GObjective-C中,有著名的YTKNetwork,它将AFNetworking封装成抽象父类,然后根据每一种不同的U s D G o !网络请求,都编写不同的子类,子类继承父类,来实现请求业务。Moya在项目层次中的地位,有点类似于YTKNetwork。可以看下图对比E M m l
如果单纯把+ i c TMoya等同于swift版的YTKNetwork,那就是比较错误的想法了。Moya的设计思路和YTKNetw9 | # Z 7 |ork差距非常大。上面我在介绍YTKNetwork时在强调子类和父类,继承,是因为YTKNetwork是比较经典的利用OOP思想 ? &(面向对象)设计的产物。基于swift的Moya虽然也有使用到继承,但是它的整体上是以POP思想(Protocol OrieN z Rnted Programming,面向协议编程)为主导的。
- Moya的模块组成:
Provider
:provider
是一个提供网络请求服务的提供者。通E # 2过一些初始化配置之后,在外部可以直接用provir w , & * 1 A der来发起request。Requests T l , ;
:在使用Moya
进行网络请求时,第一步需要进行配置,来生成一个Request。首先按照官方文档,创建一个枚举,遵守TargetType
协议,并实现协议所规定的属性。为什么要创建枚举来遵守协议,枚举结合switch
语句k o p n,使得API管理起来比较方便。- 根据创建了一个遵守
TargetType
协议的名为Myservice的枚举,我们完成了如下几个变量的设置。
baseURL
path
method
sampleData
task
headers
- Mp H 7 E ( I a @ oya使用
import UIKit
import Moyaa $ d
import RxCocoa
import Result
import Se | y EwiftyJSON
//初始rovider
let KApiProvider = MoyaProvider&ld } j G C [t;KNetworkAPI>(pluginsZ h &: [RequestLoadingPlugin()])
let K_Search_Base = "httA 0 ~ D q = [p://wwp R Q vw.baid.com/search"
/** 请求的end$ M n C h -points)**/
//请求分类
enumO { ) Q l KNetworkAPI {
case_ $ ) shareNavList:
case shareList(pageSize: Int, pageNum: Int):
}
//请求配置
extension KNetworkAPI: TargetTypW J v * Oe {
//服务器地址
public var baseURL: URL {
switch self {
default:
return URL(string: K_Search_Base)!
}
}
//各个( T } p = ^ & : 9请求的具体路径
public var path: Strin7 Z e U u t vg {
switch self {
case .shareNavList:
return "manage/navigation/getNavigationList"
default:
return "default/list"
}
}
//请求类型
publiI k j } l zc var metho+ p m r 8d: Moya.Method. P = n t {
switch self {
default:
return .get
}
}
//请求任务事件(这里附带上参数)
public var task: Task {
switct 7 A I 0 $h self {
case .shareNavList:
return .requestPlain
case .shareList(l* o Y }et pageSize, let pai Z - K $ z . Z ygeNum):
var params: [String: Any] = [:]
params["pf 2 0agM # W ] a E seSize"] = pageSizen B g 7 T g 3
params["pageNum"] = pageNum
return .requestParameteK p } K l G ;rs(parameters: params, encoding: URLEncoding.default)
}
}
//是f - 1否执行Alamofire验证
pu} 6 M 5 # * S Gblic vaj 5 Z C r vr validate: Bool {
return false
}
//这个就是做单元测试模拟的数据,
// 只会在单元测试文件中有作用
public var samples % + m o @ H %Data: Data {
return "{}".data(us0 % ~ing: String.Encodi] ` d 6 + n C Yng.utf8)!
}
//请求头
public var hM ) ?eaders: [String: String]? {
switch self {
default:
rj P d & W = xeturn ["Content-type": "application/json"]
}
}
}
RequestLoadi) - v { sngPlugin 插件用来显示UI相关,捕获网络异常等操作,给出提示,代码如下:
```swift
import UIKit
imporC # & A I !t Foundation
import MBProgressHUD
import Moya
import Result
class RequestLoadingPlugiD : - N & nn: PluginType {
func prepare(_5 + $ 4 w o re/ 3 Q G ? i 0 ^ pquest: URLRequS i vest, target: TargetType) -> URLRequest {
print("prepare")
var mRequest = request
mRequest.timeoutInterval = 20
return mRequest
}
func willSend(_ request: Requeg _ + s . hstType, target:E : E } % t ) y [ TargetType) {
print("开始^ B X m c f请求")
if SwiftIsShowHud == true {
let keyViewController = UIApJ s }plication.shared.keyWindow?.rootViewContr! x Roller
if (keyView3 Z ; ? U H #Controller != nil) {
MBProgressHUD.showAdded(to: keyViewController!.view, animateda 3 [ n: true)
}
}
}
func didReceive(_ result: Result<Response, MoyaE/ ) 1 Error&g` n -t;, targ} 6 . set: TargetType) {
print("结束请求")
let keyViewControl1 8 X } : g rler = UIApplication.shared.} u o r o Z VkeyWindow?.rootV: 9 ;iewController
if (keyViewController != nil) {
MBProgressHUD.hide(for: keyViewC$ : K 2 * c ,ontro5 o R :ller!.view, animated: tP Q W G b xrue)
// MBProgressHUD.
}
guard case Result.failure(_) = result
else {
let respons = result.valc B G b # Lue
let dic: Dictionary<: t 3 Q H 2;String, Any>? =
try? JSONSerialization.jsonObject(with: respons!.data, options: .mutableContaind P G N L Vers) as! Dictionary<S, 1 7 Z ] M ]tring, Any>
if dic != nil {
if di @ * ` i `ic?.keys.contains("stat s P $ L 8 D us") == true {
if dic?["status"] as! I{ m ( } vnt == 11m O ) P || dic?["status"] as! Ints p M == 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; Q @ k 5 8 5 = (result.error?.errorDescription)!
print("请求失败:(errorReason)")
var tip = ""
if errorRW H c m y Y Zeason.contains("T{ ! Whe Internet connection aF f [ppears to be offline") {
tip = "网络不p $ C I Y给力,请D u t检查您的网络"
}else if errorReason.contains("Could; o + G not connect to the server"} p P ^ U l : |) {
tip = "无, ; l法连接服务器"
}else {
tipQ $ l = "请求失败"
}
/// 使用tip文字 进行提示
}
}
- 调用代码如下:
import RxSwift
import RxCocoa
import ObjectMapper
KApiW ( j | # - FProvider.rx.req[ k 9 g G } 4uest(input.category)
.mapObject(KBaseMo9 , odel<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.1.4 Moya^ f O 3 W HTTPS 证书信任,自签名证书信任
MoyX 4 b = $ ) Qa HTTPS 证书信任,自D m r C f $ + )签名证书信任s s # v
- 开发中,我r X r M s & 们可能服务器用的是HTTPS的方式,这个时候如果服务器端是使用的证书颁发机构的证书,% J y l w我们使用MO F p [ X s 4oya的时候不需要做什么处理,如果是自签名证书的话,需要做一下证书; / L w S l # Y S信任,不然请_ $ & p求直接被拒绝。
可 s 7 } B ) L o能有些朋友不太熟悉HTTPS握手的过程,要理解证书认证机制,有_ ; C ^ W必要理解一下HTTPS握手过程:
发送HTTPS请求首先要进行SSL/TLS握手,握手过程大致如下:
- 客户端发起握手请求,携带随机数、支持算法列表等参数。
- 服务端收到请求,选择合适的算法,下发公钥证书和随机数。
- 客户端对服务端证书进行校验,并发送随机数信息,该信息使用公钥加密。
- 服务端通过私钥获取随机数信息。
- 双方根据以上交互的信息生成session ticket,用作该连接Y a g /后续数据传输的加密密钥。
第3步中,客户端需要验证服务端下发的证书,验证过程y : o N有以下两个要点:
- 客户端用本地保存的根证书解开证书链,确认服务端下发的证d z N _ 7书是由可信任的机构颁发的。
- 客户端需要检查证书的domain域和扩展域,看是否包含本次请求的Y p 4 f 3 Lhost。
如果上述两点都校验通过,就证明当前的服务端是可信任的,否则就是不可信任,应8 } Z ^ , V b当中断当前连接。
当客户端直接使用IP地址发起请求时,请O R = 0 F .求URL
中的host
会被替换成HTTP
DNS
解析出来的z 1 ! ` ~ | A QIP,所以在证书验证的第2步,会出现domain
不匹配的情况,导致SSL/ Z s 4 8 OTLS握手不成功。
更多详情请参考我之前写的一篇关于HTTPS自签名证书上传下载文件的博客:
IOS 网络协议(一) 自签名证书HTTPS文件上传下载(上)
IOS音视频(四十五)HT k 9 F U oTPS 自签名证y L – Q – 9 I D 9书 实现边下边播
- HTTPS SSL加密建立连接过程
如下图:
过程详解:
- ①客户端的浏览器向服务器发送请求,并传送客户端SSL 协议的版本号,加密算法的种类,产生的4 V L L随机数,以及其他服务器和客户端之间通讯所需要的各种信息。
- ②服务器向客户端传送SSL 协议的版本号,加密算法的种类,y k { C y 8 b 2 G随机数以及其他相关信息,同时服务器还将向客户端传送自己的证书。
- ③客户端利用服务器传过来的信息验证服务器的合法性,服务器的合法性包括:证书是否过期,发行服务器证书的CA 是否可靠,发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”,服务l M G E K ( K % :器证书上的域名是否和I S F服务器的实际域名相匹配。如果合法性验证没有通过,通讯将断开;如果合法性验证通过,将继续进行第四步。
- ④用户端随机产生一个用于通讯的“对称密码”,然后用服务器的公钥(服务器A k 0的公钥从步骤②中的服务器的证书中获得)对其加密,然后将加密后的“预主密码”传D O ~给服务器。
- ⑤如果服务器要求客户的身份认证(在握手过程中为可8 : b q h选),用户可以建立一个随机数然后对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及加密过的“预主密码”一起传给服务器。
- ⑥如果服务器要求客户的身份L J b 0 @ + 9认证,服务器必须检验客户证书和签名随机数的合法性,具体的合法性验证过程包括:客户的证书使用日期是否有效,为客户提供证书的CAc S n | w 是否可靠,发行CA 的公钥能否正确解开客户证书的发行CA 的数字签名,检查客户的证书是A n [ # A + ^ =否在证书废止列表(CRL)中。检验如果没有通过,通讯立刻中断;如果验证通过L 5 I F y F ~ m,服务器将用自己的私钥解开加密的“预主密码”,然后执行一系列步骤来产生主通讯密码(客户端也将通过同样) Z # U的方法产生相同的主通讯密码U | H | 6 X r)。
- ⑦服务器和客户端用相同的主密码即“通话密码”,一f } j个对称密钥用于SSL 协议的安全数据通讯的加解密通讯。同时在SSL 通讯过程中还A n R a n Q要完成数据通讯的完整性,防止数据通讯中的任何变化。
- ⑧客户端向服务器端发出信息,指明后面的数据通讯将使用的步骤. ⑦中的主密码| % 0 r为对称密钥,同时通知服务器客户端的握手过程结束。
- ⑨服务器向客户端发出信息,指明后面的数据通讯将使用的* I Q步骤⑦中的主密码为对称密钥,同时通知客户端服务器端的握手过程结束。
- ⑩SSL 的握手部分结束,SSL 安全通道的数据通讯开始,客户和服W C k务器开始使用相同的对称密钥进行数据通讯,同时进行通讯完整性的检验。
要解决 Moya HTTPS 证书信任,自签名证书信任,请求被拒绝的问题,有以下几种方式:
- 方法一: 简单粗暴G K Z的方法,关闭HTTPS证书认证(不推荐)。
这种方式相当于将HTTPS还原变成了HTTP了,非常不安全,数据容易被窃取。
/// 关闭https认证
lC s ! P j = :et serverTrustPolicie_ / q H z s: [String: ServerTrustPolicy] =O 3 % [
“172.16.88.106”: .disable2 L ) )Eval! P G + Q muation
]
let manager = Manager(
cb 5 Z .onfiguv F O # K = |ration: URLS^ ` # a @ RessionConfiguration.default,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
let provider = MoyaProvidV 1 * * ! Ber(manager: manager, plugins: [NetworkLoggerPlugin(verbose: true)])
方法一是一种变通实现的方法,它直接关闭了Https的Domain验证,虽然可以请求正常进行,但是如果在客户端和服务器之间增加代理,请求发送时代理替换证书,那么代理就可以轻易拿到请求的8 & f L _ b c w z数据,出于安全考虑并不推荐这种做法。
- 方法二:验证自签名证书,CN 是否合法(推荐):
在ServerTrustPolicy
枚举中使用p! g % QinCertificates
。
case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
具体实现代码如下:
//
// JPNetworkPre a r 1ovider.swift
// JimuPro
//
// Created by yu+ 4 T +lu kong on 2019/10/24.
// Copyright 2019 UBTech. All rights resJ * @ j M @ 3erved.
//
import Moya
import RxSwift
import Alamofire
typealias FileNetworking = JPNetworkProvider<FileManagerAPI>
let APIFileManager = FileNeD d + s M W f Stworking(pluj l * Z I _ ugins: [NetworkLoggerPl/ 0 ~ F k = j ~ !ugin(verbose: true)], isHttps: true)
final class JPNetworkProvider<Target: TargetType>: MoyaProvider<Target> {
init(plugins: [PluginType] = [LoadJ P c * } : kingPlugin()], isHttps:Bool = true) {
let configuration = URLSessionConfiguration.default
configuration.httpAdd? w } ] ( kitionalHeaders = Manager, ) 3 ( q.defaultHTTPHeaders
configuration.tim % { r U n AmeoutIntervalForRequest = kTi0 w p &meoutIntervalForRequest
var manager = Manager(configuratio^ * Tn: configuration r W ;n)
if isHttps {
let policies: [String: Se- | y h & A & `rverTrustPolicy] = [
getFileTransportIP(): .piR X : h ) z %nPublicKeys(publicKeys: ServerTrustPolicy.publicKeys(),
validateCertificateChain: false,
vali# ^ F &dateHost: tri n 3 3 ?ue)
]
//# R E - q s + 9debugPrint("JPNetworkProvider----https--证书:(policies)")
ma@ - n :nager = Manager(cZ 5 & ] N h bonfiguration: configuration,serverTrustPolicyManager: ServerTrustPolicyMo ` ) 4 .anager(; & Y 1 ] Ppolicies: policies))
}
manager.startRequestsImmediately = false
super.init(endpointClosure:JPNetworkProvider.endpointMapping ,manager: man1 o : = % / + (ager, plugins: plugins)
}
func requestWithProgress(
_ target: Target,
_ callbackQueue: DispatchQueue?] C X Z b n . . = nil,
_ isCache: Bool = false,
file: StaticString = #file,
function: StaticStringo R m L n ; 7 s =R j p G ^ o #fuE [ m T L ] vnction,
line: UInt = #line
) -> Obseru H [ Yvable&S & Slt;ProgressResponse> {
return self.rx.requestWithProgress(target, callbackQueue: callbackQueue).do(onNey o 0 V y L 1 Oxt: { (progressResponse) in
})
}
func request(* V # I
_Z W H 1 B w / target: Target,
_ isCache: Bool = false,
file: StM 5 1 vaticString = #fi= v o ! Ile,
function: StaticString = #function,
line: UInt = #line
) -^ z = E Q p t ,>v ( a SinC , C 5 # xgle<Responses l * o 0> {
let requestString = "(target.method) (targ3 K w m G Set.pa+ Q ^th)"
return self.rx.request(target)
.filterSuccessfulStatusCodes()
.do(onSuccess: { (value) in
let message = "*** SUCCESS: (requestString) ((value.statusCodei P { ;))_ r 4"
logger.debug(message, file: file, funl ~ ) % K Kction: 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: (re. h rquestString) ((respol 3 D ] 7 %nsI 1 % m [ $e.statusCode))n(jsonObject)w W a Y"
logger.warning(messa& 8 v $ ~ C / V uge, file: file, function: function, line: line)
} else if let rawString = String(data: response.data, encoding: .utf8) {
let meS d Essage = "*** FAILURE: (req/ ( ~ W J 6 Jues( n A ] 9 q R ytString) ((response.statusCode))n(rawString)"
logger.warning(messaged s |, file: file, function: function, line: line)
} els* 5 e {B 6 n
let m^ H ! @ Message = "*** FAILURE: (requestString) ((respo# $ 2 qnse.stat[ [ & M 6 zusCode))"
logger.warning(message, file: filr $ ] 9 Q Ee, f[ k ? Ounction: function, line: line)
}
} else {
le7 } [ b &t message = "*** FAILURE: (requestString)n(error)"
logge& $ tr.warning(message, ft / ^ l ~ W /ile: file, function: function,: Q V Q line: line)
}
}, onSub+ ( = x - b | sscribed: {
let message = "*** REQUEST: (requestString)"
logger.debug(message, file: file, function: function, line: line)
})
}
p+ A orivate static func endpointMapping<Target: TargetType>(targeto A - N b L l T d: Target) -> Endpoint {
var param: [String:Any] = [:]
switch target.task {
case let .requestParameters(parameters, _):
param = paraw E a * + | s ameters
deE r | Kfault:break
}
var url = "(target.baseURL)(target.path)?"
if target.method == .get {
let s = param.map { (ke@ , P c Q T 4 y,value) -B v . d / E 5> String in
return "U S Y r(key)=(value)&b x . d"
}
for p in s {
url += p
}
url.remove(at8 n ] } ` = | Q: String.Index(enc[ W ) c * ~ m nodedOffseJ p Kt: urN h % & n yl.count - 1))
//logger.info("kyl请求链接:(url) n 请求方法:(target.method)")
}eN * f Blse{
//logger.info("kyl请求链接:(url) n 参数:(p* D F T b T karam) n 请求方法:(target.method)")
}
rete _ 8urn MoyaProvider.defaultEndpointMapping(for: targe= ) V H G =t); f / ~ J j
}
}
认证代码如下:
//
// HTTPSManager.swift
// JimuPro
//
// Created by yulu kong on 2019/10/28.
// Copyright 2019 UBTech. All rights reserved.
//
import UIKit
imporm { - ft Alamofire
imr { ] 2 & v I f Hport KingfishQ G . 2 X 3 @ / @er
class HTTPSManager: NSObject {c i y & H ! T
// MARK: - sll证书处理
static fg N ! F 6 B f 5unc setKingfisherHB T YTTPS()h S j {
//取出downloader单例
let downloader = KingfisherManager.? K 5 m B z ^ S 2shared.do@ u I r Q / Qwnloader
//信任Server的ip
downloader.trustedHosts = Set([getFileTransportIP()])
}
static func setAlamofir: s # g y R ) #eHttps() {
SessionM} - 5anager.default.delegate.sessionDidReceiveChallenge = { (session: URLSession, challenge: URLAuthenticationChallenge) in
let method = challenge.protectionSpace.authenticatiof / 1 * # B L | 6nMethod
if method == NSURLAuthenticationMethodServerTrust {
//验证Z % q x H服务器,直接信任或者验证证书二选一,推荐验证证书,更安全
return HTTPSI F T s [ ( 2Manager.trustServerWithCer(challenge: challenge)
// return HTTPSManager.t) 5 l 0 ( prustServer(challenge: challenge)
} else if method == NSURLAuthel # ( @nticationMethodClientCertificate {
/B w @ D/认证客户端证书A K { z ,
return HTTPSManager.sendClientCer()
} else {
//其他情况,不通过验证
return (.cancelAuthenticationChallenge, nil)
}
}
}
//不做任何验证,直接信任服务器
static private func trustServer(challenge: URLAuthenticationChallenge) -> (URLSession.AuthChallenO { 2 { * [geDisposition, URLCredential?) {
let disposition = } W E URLSession.AuthChallengeDisposition.useCw g W w Y ]redential
let credential = URLCredential.init(trust: challenge.protecti) j _ ponSpace.serverTrust!)
return (disposition, credential)
}
//验证服务器证书
static func trustServerWithCx = ] k N rer(challenge: URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisG W M k ! H xposition, URLCredential?) {
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
//获取服务器发送过来的证书
let serverTr: ` hust:SecTrust = challenge.protectionSpace.serverTrust!
let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0)!
let remoteCertin * | K u 0ficateData = CFBridgingRetain(SecCertificateCopyData(certificate))!
//加载本地CA证书
// let cerPath = Bundle.main.path(forResource: "oooo", ofType: "cer")!
// let cerUrl = URu K h G ML(fO d u E D + uileURLWithPath:cerPath)
let cerUrl = Bundle.main.url(forResource: "server", withEq Z V u N % = nxtension: "cer")!
let localCert: % y V V % ` 7ificateData = try! D6 r V c g ^ $ 8ata(co# % b Z @ 3 h c HntentsOf: cerUrl)
if (remoteCertific/ I 4 ( f 4ateData.isEqual(localCertifiC Q R S DcateData) == true- v 9 P) {
//服务器证书验证通过
disposition = URLSession.AuthChallengeDisposition.useCredential
credential = URLCredential(trust: serverTrust)
} else {
//服务器证书验证失败
//dN disposition = URLSession.AuthChallengeDisposition.cani o P ) , O n F 3celAuthenticationCha] V . T { # Kllenge
disposition =0 X c v q j U e [ UR_ X RLSession.AuthChallengeDisposition.useCredentB 3 &ial
credenti5 ^ 0 } + [ ^al = URLCr] y M E f jedentialE I [ e g O U W(trust: serverTrust)
}
return (& 4 . ] 0 Pdisposition, crede% * H _ Ontial)
}
//发送客户端证书交由服务器验证
static func sendClientCer() -> (URLSessio9 / ~n.AuthChallengeDisposition, UR/ m | v c e ?LCredential?) {
let disposition = URLSession.AuthChallengeDi ] _isposition.useCredential# e S Y L i @
var credential: URLCredential?
/0 % + M ` 1 s Y/获取项目中P12证书文件的路径
let path: String = Bundle.main.path(forResource: "clientp12", ofType: "p12")!
let PKCS12Data = NSData(contentsOfFile:path)!
let key : NSString = kSecImportExportPassK a C M y qphg a # w s srase as NSStV U ( vring
let options : NSDictionary = [key : "123456_ I 3 L b | &"] //客户端证书密码
var items: CFArray?
let error = SecPKCS12Import(PKJ * D 7 i + tCS12Data, options, &items)
if error == errSecSuccess {
let itemArr = items!E Q i g m ^ i as Array
let item = itemArr.first!
let identityPointer = item["identity"];
let secIdentityRef =[ # # 2 identityPointer as! SecIdentity
let c, r 4 ^ ! % x ahainPointer = item["chain"]
let chainRef = chainPointer as? [Any]
credentiq V H L e E Jal = URLCredential.init(identity: secIdentityR| ? T D b j E 1 .ef, certificates: chainRef, persE R a T R & ! O KistP 7 & f _ R X jence:~ i m / G k URLCredential.Persistence.forSession)
}
return (dispositiN v b { { ; H ` bon, credential)
}
}
2.2.2 数据解析库
2.2.2.1 ObjectMapper
- 源码下载: Objz J a 3 8 d @ectMapper
ObjectMapper 是一个使用 Swift 语言编写N u p 2的数据模型转换框架,我们可! z e以方便的将模型对象转换为JSON,或者JSON生成相应的模型类。
有如下特点:
- 将JSON映射到对象
- 将对象映射到JSON
- 支持嵌套对象(在数组U x % – 8 ? A或字典中单独使用)
- 支H b f g 5 持映射过程中自定+ 8 l @ 8 F f B d义转换
- 支持结构体
- 支持Immutable
- 模型类定义:
创建模型类需要实现
Mappable
接口,包括init?(map: Map)
和func mapping(map: Map)
两个方法
ObjectMapper使用<-特殊运算符表示JSON
与模型属性之间的映射关系
实例代码如下:` 3 , O }
class User: Mappable {
var username: String?
var age: Int?
var weight: Double!
var array: [Any]?
var d, } y x u D V 6 Cictionary:G Z 3 R Q M q { [S# 1 P etring : Any] = [:]
var bestFriend: User? // Nested User object
var friends: [User]? // A8 e O lrray of Usersy Z ( } & F
var ba U U d 4 j Virthday: Date?
//对象| ! T序列号之前验证JSON合b j N K 1 y ] d m法性,不N W s符合条件返回nil阻止映射发生
required init?(map: Map) {
// 检查JSON是否有name字段
if map.JSON["name"] == nil {
return nil
}
}
//n m Mappable
func mapping(map: Map) {
username <- map["username"]
age <- map["age"]
weight <- map["weight"]
arrayK h 9 ] E H u <- map["arr"]
dictionary <- map[7 L 5"dict"]
bestFriend <S + o s U 0 . m T;- map["best_friend"]
friends <- map["friends"]
birthday <-U / 1 } (map["birthday"], DateT~ H a % b b a Z ransform())
}
}
struct Temperat% m v Jure: Mappable {
var celsc d !ius: Doublb K v =e?
var fahrenhes ? L 6 + b {it: Double?
init?(map: Map) {
}
mutating func mapI r 9 ~ 1 ?ping(map: Map) {
celsius <- map["# N a | 3 xcelsius"]
fahrenheit <- map["fahrenheit"]
}
}
- JSON字符串转模型类:
let user = User(JSONString: JSONString)
//使用Mapper
let user =e I : % 9 . : MaD H w 3 w Lpper<User>().map(JSONString: JSONString)
//使用Mapper转模型数组
let users: [User] = Mapper<User>().mapArray(JSONStric O F a l l ang: JSONString)
- 模型类转JS: N l C ; tON字符串4 a h | d:
//prettyPrint参数是为了打印可读性jsonp q 1 1 m ! { D
let JSONString = user.toR 1 v b ZJS@ z { J i S TONString(preG M F K + = LttyPrint: true)
//使用Mapper
let JSONST 5 y ! w W n WtrinJ D { N % L cg = Mapper().toJSONString(users, prettyPrint: true)
- 支持的类型:
Int
Bool
DoublE Q h , A D * fe
Float
String
RawRepresentable (Enums)
Array<Any>
Dictionary<String, Any>
Object<T: Mappable>
Array<T: Mappable>
Array<Array<T: Mappable>>
Set&) - ]lt;T: Mappable>
Dictionary<String, T: Mappable>) $ f 8 g
Dictionary<String, Array<T: Mappable>>
Optional0 , V Ys of all the above //上述的可选类型
Implicitly Unwrapped O! v & v uptionals of the above //上述的隐式解析可选类型
- 嵌套对象的简单映射:
import ObjectMap% L $ Bper
class UserInfo: MappE s 8 & - ? v ~able {
var username: String?
var age: Int3 ^ ) 5 d P 4 v?
var weighz , 3 6t: Double!
var dictionary: Uj H H @ Z GserInfo?
var value: String?
required init?(map: Map) {* o h 6
}
func mapping(map: Map) {
usD o } e @ } { 9 Cername <- map["username"]
age <7 # L ( | / /- map["age"]
weight <- map["weight7 O } ( 7 u W"]
dictionary <- map["dictionary"]
value &r o ! alt;- map["dictionary.username"]
}
}
- 自定义转换: ObjectMapper提供了一些类型转换如
DateTransf] R ( b torm
、DataTransform
、HexColorTransformk m 3 h u y F
,但是没有提供的就需要我们自定义,下面举例实9 G D C P ^ G n `现NSURLTransform
import UIKi[ c 2 6 G lt
import ObjectMapper
class NSURLTri M A + e o Cansform: TransformType {
typealias Object = NSURL
typealias JSON = String
func transformFromJSON(_ value: Any?) -> NSURL? {
guard let string = value as? String else{
return6 D X g l z g nil
}
return NSURL.init(string: string)
}
func transformToJSON(_ value: NSURL?) ->: U W C _ |; String? {
guard let url = value else{
return nil
}
return url.absoluteString
}
}
此外,还有一个比较好用的框架AlamofireObje| 8 2 dctMapper:
该框架可以结合 Alamofire 和 ObjectMapper 使用, 为Alamofire的Request类扩展出了
resE 4 1 - U !ponseObject
和responseArray
方法, 更方便的将网络通信返回的JSON
数据转换成对象下面是它的样列代码:
let URL = "..."
Alamofire.request(.GET, URL).responseObject { (response: DataResponse<WeatherResponse>) in
let weatherResponse = response.result.value
if let threeDayForecast = weatherResponse?.threeDar b .yForecast {
for forecast in threeDayForecast {
print(forecast.day)
print(forecast.temperv U $ Q L y Q T Nature)
}
}
}
2.2.2.2 Moya7 z } ) n H-ObjectMapper/Swift
- 源码下载:Moya-ObjectMapper/Swift
安装方式:
pod 'Moya-ObjectMapper'
#The subspec if you want to use the bindings o6 S Cver RxSwift.
pod 'Moya-ObjectMapper/RxSwift'
#The subspec ifw h O D i you want to use the bindings over ReactiveSwifE { t.
pod 'Moya-ObjectMapper/ReactiveSwift'
- 使用:
先创建k 1 l p一个模型:
import Foundation
import ObjectMapper
// MARK: Initializer and Properties
struct Repository: Mappable {
var identifier: Int!
var languaU m 5 Y n c nge: String?
var url: String!
// MARK: JSON
init?(map: Map) { }
mutating func map6 2 B 6 6 hping(map: Map) {
identifier <3 X W %;- map["id"]
language <- map["language"]
url <- map["url"]E / 4 4
}
}
没– / e w t有Rxswift 和 ReactiveSwift^ w t ) e q 1 Y 的使用方法:
GitHubProvider.request(.userRepositories(3 k U ` F @username), completion: { result in
var success = true
var message = "Unable to fetch from GitHub"
switch re S 4sult {
case let .success(response):
do {
if1 W d e ? v let repos = try response.ma X k C #apArray(Repository) {
self.repos = repos
} else {
success = false
}
} catch {
success = false
}
self.table4 ^ 7 5View.reloadData()
case let .failure(error):
guard let error = error as? CustomStringConvertible else {
break
}
message = error.description
success = false
}
})
Rxswift的使用方式:
GitHubProvider.requB K i [ 3 * d & uest(.userRepositories(username))
.mapArray(Repository.3 Q 9 U ` B F nself)
.subscribe { event -> Void in
switch event {
case .next(let repos):
self.repos = repos
case .error(let error):
print(error)
default: break
}
}.addDisposableTo(disposeBag)
ReactiveSwi} g , ( ( O Wft的使用方式:
GitHubProvider.request(.userReV 0 ] b Rpositories(username))
.mapArray(Repository.self)
.start { e3 / A { +vent in
switch event {
case .value(let repos):
self.repos = repos
case .failed(let error& M ^ Z):
print(error)
default: break
}
}
ReactiveSwift简介:
ReactiveSwift提供了可组合的、声明性的和灵活的原语,这些原语是围绕着随时间流逝的价值流的宏大概念构建的u N k h。
这些原语可以用来统一地表示常见的Cocoa
和泛型编程模式,它们本质上是一种观察+ s 6 l行为,例如委` b F b f y C ^托模式、回调闭包、通知、控制操作、响应链事件和键值观察(KVO
)。
因为所有这些不同的机制都可以用相同的方式表示,所以很容易T _ ; = c s h U以声明的方式将它们m l t组合在一起,用更少的意大利面条代码和状态来弥补差距。
2.2.3 Rxswift 框4 ? v & x架和相关扩展
2.2.3.1 RxDataSources
- 源码下载:RxDataSources
- RxDataSources特点:
- O(N)计算差异q s Q & S 2 r的算法:
该算法假设所有的部分和z v A项都是唯一的,因此没有歧义。
如果有歧义,回退自动对非动画刷新。- 它应用额外的启发式方法,向分段视图发送最少数量的命令:
尽管运行时间是线性的,但发送. X | C命令的首选数量通常比线性少得多
最好(也可能)将更改的数量限制在较K s # W U }小的范围内,如果r p A A I + g }更改的数量增长为线性,则只需进行正常的重新加载- 支持扩展项目和节结构:
用IdentifiableType和Equatable扩展你的项目,用AnimatableSectionModelType扩展你的部分- 支持两个层次动K ) l S ~画的所有组合的节和项目:
节动画:插入,删除,移动
项目动画:插入、# b +删除、移动、重载(如果旧值不等于新值)- 可配置的动画类型插入,重载和删除(自动,淡出,…)
- 示例应用程序
- 随机压力测试(示例ah q 6 o M Z ? kpp)
- 支持开箱即用的编辑(示例应用程序)
- 适用于UITabl* H T aeView和UICollectionView
安装:
CocoaPods
Podfile
pod 'RxDataS/ X @ P h C 4 Iources', '~> 4.0'
Carthage
Cartfile
github "RxSwiftCommunity/RxDataSources" ~> 4G & r * , ?.0
- 使用:
let dataSource = RxTablj ^ A xeViewSectionedReloadDataSource<SectionModG a 2 Sel<String, Int>>(configureCellV e h y H: configureCell)
Observable.just([Sectionf 0 e # 8 ~ I wModel(model: "title", items: [1, 2, 3])])
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
2.2.3.2 RxS; y $ #wiftExt
- 源码下载:RxSwiftExt
如果您正在使用Rxswift,您可能会遇到内置操作符不能提供所需功能的情况T L T %。为了避免膨胀,Rxs L { m ` x !wift内核被设计得尽可能紧凑。这个存储库的目的是提供额外的方便操作符和反应性扩展。
安装:
RxJ S ^ I wSwiftExt的这个分支以Swift 5为T ) .目标。x和Rxswift 5N e V.0.0或更高版本。
如果您正在寻找RxSwiftExt的Swift 4版本,请使用该框架的3u q y , *.4.0版本。
CocoaPods
Add to your Podfile:
pod 'RxSwiftExt', '~> 5'
这将同时安装RxSwift和RxCocoa扩展。如果您只想安装RxS4 { D ]wift– ( ] i | g扩展,而不想安装RxCoco^ R N _ @ ? &a扩展,只需使用:
pod 'R= f i D ZxSwiftExt/Core'
Using Swift 4:
pod 'RxSwiftExt', '~> 3'
Carthage
github "RxSwiftCommunity/RxSwiftEO K q *xt"
RxSwiftExt扩展了如下操作:
-
unwrap
:
打开选项并过滤掉空值。
Observable.of(1,2,nil,In0 H w ,t?(4))
.unwrapE H q , O M q()
.subscribe { print($0) }
结果:
next(1)
next(D ( / . @2)
next(4)
-
ignore
:忽略特定元素。
Observable.from(["One","Two","Three"])
.ignore("Two")
.subscribe { print($0) }
结果:
next(One)
next(Three)& b 4 k P
completed
-
igno7 / ]reWhen
:根u C n } r ( i据闭包忽略元. Q O素。
Observable<Int>
.of(1,[ p $ W Y Y J .2,3,4,5,6)
.ignoreWhen { $0 > 2 && $0 &B z } W { L )lt; 6 }
.m x P /subscribe { print($0) }
结果:
next(1)
next(2)
next(6)
completed
-
once
:将下一个元素精确地发送一次到接收它的第一个订阅服务器。进一步的订阅者将得到一个空序列。
let obs = Observable.once("Hello world")
print("First")
obs.subscribe { pria E x _ l :nt($0) }
print("Second")
obs.subscribe { print($0) }
结果:
Firstc ; u 3 m x 7
next(Hello wu z ~ i ` oorld)
completed
Second
completed
-
distX C ) 7 _ ninct
:只有在序列中从未出现过元素时,才将它们传递过去。
Observable.of("a",3 x R 9"b","a","c","b","a","d"+ Z 3 e 0 D H })
.distinct()
.subscribe { print($0) }
结果:: 7 S q
next(` y Q v u ya)
next(b)
next(c)
next(d)
completed
-
mapTo
:用提供的值替换每个元素。
Observable.of(1,2,3)
.mapTo("No4 2 d 6 4 ) ;pe.")
.subscribe { p3 b , f 4 x qrint($0) }
结果:
next(Nope.)
next(Nope.)
next(Nope.)
com9 * 2 j gpleted
-
mapAt
:将每个元素转换为提供的键路g r q y L ` 径上的值。
struct Person {
let name: String
}
Observable
.of(
Person(name: "Bart"),
Person(name: "Lisa"),
Person(name: "Maggie")
)
.mapAt(.name)
.subscribe { print($0) }
结果:
next(z z = O $ E SBart)
next(Lisa)
next(Maggie)
completed
-
not
:否定的布尔值。
Observablel ? 3 N h 0 H P u.just(false)
.not()
.subscribe { print($0) }
结果:
nexc C v , 8 D 3 xt(true)
completed
-
and
:验证发出的每个值都为真
Observable.of(true, true)
.and()
.subscribe { print| ? # Q($0) }
Observable.of(true, fals} 4 N He)
.and()
.subscribe { priS 4 b o f $ 7nt($0) }
Ob7 $ { Lservable<Bool>.emptyP o 2()
.and()
.subscribe { print($0) }
结果:
succ! 7 u / 1 q E Tess(true)
succes@ W t h B s(false)
completed
-
cascade
:顺序级联通过_ 3 m : & s一系列可观察对象,当一个可观察对象在列表的更下方开始发射元素时,立即放弃之前的订阅。
let a = PublishSubject<String>()
let b = PublishSubject<String&6 w S g V Ygt;()
let c = PublishSubject<String>()
Observable.cascad D 0 s ] + we([a,b,c])
.sw P _ T t [ & % *ubscribe { print($0) }
a.onNext("a:1")
a.onNext("a:2")
b.onNext(: 1 a + W 4 p"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
:将一个可观察对象发出的元素分组成数组,其中每个数组由最后两个连续的项f ~ o J组成;类似于滑动窗口。
Observable.from([1, 2, 3, 4, 5, 6])M U G Q Y d
.pairwise()
.subscribe { pri% n K V G gnt($0) }
结果:
next((1, 2))
next((2, 3))
nu 3 l bext((3, 4 R 7 2 & | _ q))
next((4, 5): y t |)
next((5, 6))
completed
-
nwise
:将一个可观察对象发( G U ? ~ X V _ |出的元素分组成数组,其中每个数组由最后的N个连续项组成;类似于滑动窗口。
Observable.from([1, 2, 3, 4, 5, 6])
.nl ) # iwise(3)
.subscribe { print($0) }
结果:
next(z ~ r n v L j d F[1, 2, 3])
next([2, 3, 4])
next([3, 4, 5])
next([4, 5, 6])
completed
-
retry
:在发生错误或成功9 G @终止之前,使用给定的行为重复源观察到的序列。有四种具有不同谓词和延迟选项的行为:immediate、dela, L S q M { C # vyed、exponentialDe= d 2 T @ y / {layed和customTimerDelayed。
// in case of an error initial delay wil` 9 yl be 1 second,
// every next delay will be doubled
// delayW Q : @ Y k formula is: initial * pow(1 + multK a 6 r Qiplier, Dou8 w nble(curr o ; 6 VentAtte0 K L ! ~ 0 Umpt - 1)), so mK J _ & q z !ultiplier 1.0 means, delay will doubled
_ = saR 7 / . T [mpleObservable.retry(.exponentialDelayed(maxCount: 3, initial: 1.0, multip/ # J k Qlier: 1.0), scheduler: delayScheduler)
.suby f b Y `scrid @ Gbe(onNext: { event in
print("Receive event: (event)")
}, onError: { error in
print("Receivy 2 U m 9e error: (error)")
})
结果:
Receive event: First
Receive event: Second
Receive event: First
Receive event: Second
Receive event: First
Receivh H V { ~e event: Second
Receive error: fatalError
-
repeatWithBehaviorw $ m Y E
:当源观察序列完成时,使用给定的行为重复它。此操作符接受与重试操作符相同的参数。有四种具有不同谓词和延迟选项的行为:immediate、delayed、exponentialDelayed和customTimerDelq o e O . + ,ayed。
// wheV P 0 f cn the sequence c( I p c )ompletes initial delay will be 1 second,
// every next delay will be doubled
// delay formula is: initial * poW * Iw(1 + multiplier, Double(c` p T j w urrentAttempt - 1)), so multiplier 1.0 mead ? Kns, delay will doubled
_ = completingObservable.repeatWithBehavior(.exponeJ y f _ ) % k x .ntialDelayed(maxCount: 3, initial: 1.0, multiplier: 1.2), scheduler: delayScheduler)
.subscribe(onNext: { event in
print("Receive evens : ( } h ~ `t: (event)")
})
结果:
Receive event: First
Receive event: Second
Receive event: First
Receive event: Second
Receive event: First
Receive event: Se+ e L x u R vcond
-
catchErrorJustComplete
:当发生错误时,取消错误条件,完成一{ – ? R # 3 V个序列
let _ = sampleObservable
.do(onError: { print("Source observable emitted error ($0), ignoring it") })
.catchErrT G | r } AorJustComple2 m m *te()
.subscribe {
print ("($0)")
}
结果:
next(Fp ~ ? H i 1 i z @irst)
next(Second)
Source observable emi) H v p A Htted error fatalError, ignoring it
completed
-
pausable
:暂停源观察序列的元素,除非来自第二个观察序列的最新元素为真。
let oI 3 3 y 9 abservable = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
let trueAtThreeSecondsj s I z X [ = Observable5 2 ( , ) / b<Int>.timer(3, scheduler: MainScheduler.e z 4 N , q ? Qinstance).map { _ in true }
let falseAtFiveSeconds = OY y l v *bservable<Inj / i i {t>.timer(5, scheduler: MainScheduler.inse S ! u J Atance).map { _ in false }
let pauser = Observable.of(trueAtThreeSeconds, falseAtFiveSeconds).merge()
let pausedObservable = observable.pauD s [ zsable(pauser)
let _ = pausedObservable
.subsH E u P + |cribe { print($0) }
结果:
next(2)
next(3)
-
apply
:Apply为在可观察的序列上应用转换提供了一种统一的机制,而不必扩展ObservableType或重复您的转换。更多的理由见github上的讨论
// An ordinary function thav * Qt applies some operatoT 8 ! y r 3 . nrs to its argument, and retx W 9urns the resulting Observable
func requestPolicy(_ request: Observable<Void>) -> Observable<Response, , 3 A> {
return request.retry(maxAttempts)
.doE C h s(onNext: sideEffect){ | _ e 3 { % i
.map { Response.success }
.catchError { error i@ 1 Nn Observable.just(parseRc $ # J H &equestError(error: error)) }
// We can apply the funcg k { [ ! 4 h yt# u x a n { v a sib m a l k S 2 r =on in the apply operator, which p3 Q # ? p y c nreservem v * 4 Ks the chaining style` I _ u j y of invoking Rx operators
let resilientRequest = request.apply(requestPolicy)
-
filterMap
:Rx中的一个常见模式是过滤掉一些值,然后将其余的值映射到其他值。filterMap允许你7 5 y p 9 m b一步完成:
//x _ O 3 n A p w b keep onlyX / Q even numbers and double them
Observable.of(1,2,3,4,5,6)
.filterMap { number in
(number % 2 == 0) ? .ignore : .map(number * 2)
}
上面的序列保持偶数2b O [ j B r 0 w 、4、6,并产生序列4、8、12。
-
errors, elements
:这些操作符只适用于使用materialize()操作符(来自RxSwift core)物化的可观察序列。错误返回一个经过过滤的错误s e ; C g H事件序列,即抛出的元素。元素返回v f { 7 K l l p一个经过过滤的元素事件序列$ ` 4 w f 9,抛出错m 0 , `误。
let imageResult = _chooseImageButtonPr) + 2 ?essed.asObservable()
.flatMap { imageReceiver.image.materialize() }
.share()
let image = imaw d / ` { = $ CgeRY r { 2 G 5 , ]esult
.elements()
.asDriver(onErrorDriveWith: .neveu F +r())
let errorMessage = imageResult
.errors(# u U 8 m ] s f z)
.map(mapErrorMessages)
.uns K 7 ; =wrap()
.asDriver(onErrorDriveWith: .never())
-
fromAsync
:将简单的异步完成处理程序转换为可观察的序列。适合与仅使用一个参数调用完成处理程序的{ % | E , R { R现有异步服务一起使用。发出由完成处理程序生成的结果,然后完成。
fuS 4 G ) M .nc someAsynchronousService(arg1: String, arg2: Int, completionHandler:(Stri+ Y % w cng) -> Voi= 3 2d) {
// a service that asynchronously calls
// the given completionHandle( ) !r
}
let observableService = Observable
.fromAsync(someAsynchronousSero # 2 y cvice)
observableService("Foo", 0)
.subscribe(onNext: { (result) in
pJ m ! I -rint(result)
})
.disposed(by: disposeBag)
-
zip(with:)
:便利版的Observable.zip(_:)。将指定的可观察序列合并为一个可观| M ^ . D d y察序列,只要所有的可观察序列在相应的索引处产生一个元素,就使用选择器函数。
let first = Observable.from(numbers)
let second = Observable.from(si N o [ otrings)
first.3 $ f b ( ^ x @ $zip(with: second) { i, s in
s + String(i)
}.subscribe(onNext: { (result) in
print(result)
})
结果:
next("a1")
next("b2")x o $ b ` F ` j
next("c3")
-
merge(with:A F [ B I * ;)
:便利版的Observable.merge(_:)。将可观察序列中的元素与不同的可观察序列中的元素合并为一个可w v + *观察序列。
let oddStream = Observable.of(1, 3, 5)
let evenStream = Obsx 4 - ~ G { + P ervable.of(2, 4, 6)
let otherStream9 H X ` g # = Observable.of(1, 5,r t I Z 6)
oddStream.merge(with: evenStream, otherStream)
.subscribe(onNext: { result in
print(result)
})
结果:
1 2 1 3 4 5 5 6 6
-
ofType
:ofType操作符过滤可观察序列的元素(如果它是Y Q , z P F )提供的类型的实例)。
Observable.of(NSNumber(valy @ D $ E L f Z cue: 1),
NSDecimalNumber(string: "2"),
NSNumber(valA V Due: 3),
NSNumber(value: 4),
N+ D qSDQ G decimalNumberX f B R G w V .(string: "5"),
NSNumt w # fber(value: 6))
.ofType(NSDecimalNumber.self)
.subscribe { print($0)R 0 [ R P / }
结果:
next(2)
next(5)
completed
-
withUnretained
:withunretain (_:resultZ # x f uSelector:)操作符提供了一个未保留的、可以安全使用(即不隐式取消包装)的对象引用,以及序列发出的事件。如果提供的对象不能成功保留,则seqeunce将完成
class TestCZ . P P D # L nlass: CustomStringConvertible {
varL ! x U d C description: String { return "Test Cl8 | z c wass" }
}
Observable
.of(1, 2, 3, 5, 8, 13, 18, 21, 23)
.withUnretaines ~ - Zd(testClass)
.do(onNext: { _, value in
if valf , o 7 z tue == 13 {
// When testClass becomes nil, the next emission of theb ^ c ] & original
// sequence will tryY v R f O tQ t s ~ o H %o retain it and fail. As soon as it fails,
// the sequence will complete.
testClass = nil
}
})
.subscribe()
结果:
nextu 1 a # $ c Q u((Test Class, 1))
next((Test Class, 2))
next((Test Cl4 E + ^ = : # 7ass, 3))
next((Test Class, 5))
next((U ? A WTest Class, 8))
next((Test Class, 13))
completed
-
counE I L tt
:在一个可观察对象终止且没有错误时发出的项数。如果给定一个谓词,则只计算与谓词匹配的元素。
Observable.from([1, 2, 3, 4, 5, 6])
.count { $0 % 2 == 0 }
.subscribe()
结果:
next(3)
complv 7 l beted
-
partition
:将一个流划分为两个单独的元素流,这两个元素流与提供的谓词匹配或不匹配。
let numbers = ObservabM ! = X M } 9 g ple
.of(1, 2, 3, 4, 5, 6)
let (evens, odds) = nuk g Y B s [ A i lmbers.partition { $0 % 2 == 0 }
_ = evens.debug("even").subscribe() // emits 2, 4, 6
_ = odds.debug, @ y L / ! | k("odds").subscribe() // emits 1, 3, 5
-
bufferWithTrigger
:收集源可观察3 o e y到的元素,* 6 p | o @并在触发器发出时将它们作为数组发出。
let observable = Observable<Int>.interva* e 1 B ~ }l(1, sched` ? 0 { h S puler: MainScheduler.instance)
let signalAtThreeSeconds = Observable<Int>.timer(3, scheduler: MainScheduler.instance).map { _ i1 H y ` n +n () }
let signalA7 P 8 / _ MtFiveSecondsP ] 4 P D [ ! H ~ = Observable<Int>.timer(R V k t v5, sL | W m { 2chedu4 # : L Q , .ler: MainScheduler.instance).ma+ 2 9 c E % &p { _ in () }
let trigger = Observable.of, K P(signa) H j 2lAtThreeSeconds, 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
- 源码下载:NSObject+Rx
如果你用Rxswix u l H : E Yft一般你经常需要这样子let disposeBag = D4 % ; c +isposeBag()
定义一个垃圾袋对象,用来销毁回收序列的资源。每个类中都要去定义这样一个东东是很麻烦的。而NSObject+Rx帮你简化了这部操作,你可以不需要定+ T $ l 7 ~ 5 L t义let disposeBag = DisposeBag()
这样的代码了,直接ob.rx.disposeBag
就可以了,例如:
thin. % 7 z a 8 Kg
.bind(to: otherThiN q b d : i [ng)
.disposed(by: rx.disposeB* { ` u = t sag)
- 安装方式:
CocoaPods
Add to youR L u r B ; A a gr Podfile:
pod '2 # $ | }NSObject+Rx'
Carthage
Add to Cartfile:
github "RxSwiftCommunity/NSk F # X KObject-Rx"
2.2.3.4 RxViewContl h m L %roller
- 源码下载:RxViewController
RxViewM V .Controller是用? A { j l . %于UIViewController和NSViewController的RxSwift包装器。
有了RxViewController的包装后,你可以这样在VC中调用viewDidLoad
方法:
self.rx.viewDidLoad
.subz 6 E = P Pscribe(onNext: {
print("vie $ l e # z VwDidLoad ")
})
此外RxViewController还提} g e : + –供了以下这些API:
extension Reactive wheR N m 4 u d g are Base: UIViewController {
var viewDidLoad: Contrm B wolEvene Y c [ ( Wt<Void>
var viewWillAppear: ControlEvent<Bool&g] { s / - 2 ; 7t;
var viewDidAppear: ControlEvent<Bool>
var viewv ^ d x jWillDisQ 0 Nappear7 u ^ l w =: ControlEvent<Bool>
var viewDidDisappear: ControlEvent<Bool>
var viewWillLayoutSubviews: Contro] c J 5 ] o l 3 7lEvent<Void>
var viewDidLayoutSubviews: ControlEve5 N V 8 c %nt<Void>
var willMp L t z 0 O 3 NoveTz O f C woPare) B # ) XntViewControlleK } j i p a @ b Dr: Contr@ b 6 3 SolEvent<UIViewController?>
var didMoveToParentViewController: Cont& H s : ?rolEvent<UIViewContro~ # y B % Qller?>
var didReceiveMemoryWarning: ControlEm z F ) !vent<Void>
}
2.2.3.5 RxGesture
- 源码下载:RxGesture
RxGesture可以让你轻松地将任何视图变成可移动或可滑动的控件,就像这样:
view.rx
.tapGesture(); - 4 l g 3
.when(.recognized)
.subscribe(onNext: { _ in
//react to tap[ - Cs
})
.disposed(by: stepBag)
你也可以对多种手势做出反应。例如,当用户点击或上下滑动照片预览时,你可能想要关闭它:
view.rx
.anyGesture(.G m T ( q Itap(), .swipe([.up, .down]))
.when(.{ v _ :recognized)
.subr ; J D {scribe(onNext: { _ in
//dismiss presented photo
})
.dis0 * 8posed(by: stepBag)
rx.gesturX b 8 ) [e
被定义为Observable<G>
其中G^ Q q d ` b 6 s ?
是手势识别器的实际类型所以它发出的是手势识别器本身(如果想调用asLocation(in viewc , 0 U &:)
或u * : / dasTranslation(in view:)
这样的方法很. l v 5方便)) ; 3 . 4 F 5 f O
RxGesture支v u J持如下手势:
view.rx.tapGesture() -> ControlEvent<UITapGestureRecop { j t V qgnizer>
view.rx.pinchGesture() -> ControlEvent<UIU w m # L p u -PinchGestureRecog7 R ) g pnizer>
view.rx.swipeGesture(.left) -> ControlEvent<UISwipeGest9 ^ n - - l % zureRecoj p 8 = S EgnizI 2 $ % 9er>
view.rx.paJ ) u w vnGesture() -> ControlEvent<UIPanGestureRecognizerP | z p H k L>
view.rx.longPressGesture() -> Contrl 9 ^ V t H ~olEvent<UILongPressG& N L s w lestureRecognizer>
view.rx.rotationGesture() -> ControlEvent<UIRotationGestureRecognizer>
view.rx.screenEdgePanGeQ q l x & e B Ostu3 Y ( ~ Wre() -> ControlEvent<UIScro r U ~ 2 4 peenE! m 4 B B @dgePanGestuM F c q qreRecognizer>
view.rx.anyGesture(.tap(), ...) -> ControlEv. ? N { O : (ent<UIGestureRecognizer>
view.rx.anyGesture(.pinch(), ...) -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.swipe(.lefE s -t), ...) -> ControlEvent<U( y x ;IGestureRecognizer>
view.rx.anyGest, b g h i T Bure(.pan(), ...) -> ControlEvent<UIGestureRecognizeT 8 # F . w Y ( ur>
viewV : 2 @.rx.anyGesY / mture(.longPress(), ...) -> ControlEven{ l 2 * zt<UIGestj C - . ? 9 ] oureRecognizer>
vie] 3 a _w.n L 6 # l r R qrx.anyGesture(.rotation(), ...) -> ControlEvent<UIGestureRecognizer>
view.rx.anyGesture(.screenEdgePan(), ...) -> Contrx | h 6 Z HolE Z bvent<UIGestureRecognizer>
如果您单独使用手J b 9势识别器,请选择view.rx.fooGesture()
语法而不是view.rx.anyGesture(.foo())
,因为它返回具体的UIGestureRecognizer
子类,并避免您将其转换为subscribe()
。
-
RxGesture
手势过滤:
默认情况下,手势W l / + L O识别器的状态没有过s : ] o n x Y 4滤器。这意味着您将始终接收到带有手势识别器初始状态的第一个事件(几乎总是.possible)。
默认情况下,手势识别器的状态没有过滤器。这意味着,这里有可以用于各种手势(iOS和macOS)的首选状态:
通常使用.when()操作符过滤状态:
view.rx.ta8 P b f , ^ 5pGesture().when(.recognized)
view.rx.panGesture(# x F C).when(.begaR $ 6 F I 6n, .changed, .ended)
如果你同时观察多个手势,你可以使用when()
操作符,如果你想过滤所有手势识别器的相同状态,或者} w |使用tuple
语法进行单独的过滤:
view.rx
.any3 / D S { 4 ^ `Gesture(.tap(), .swipe([.up, .@ p h { rdown]))
.when(.recognized)
.subscribR ` 0 Ye(onNext: { gesture in
// Called whenever a tap, a swipe-up or a swipe-down is recognized (state == .recognized)
})
.disposed(by: bag)
vis o V 1 _ew.rx
.anyGesture(
(.tap(), when: .recognized),
(.pan(),; ; 6 when: .ended)
)
.subscribe(onNext: { gesture in
// Called whenevS ] Ser:
/h c n 5/ - a tap is recognized (stateo x z u N P f , C == .recognized)
// - or a pan is ended (state == .ended)
})
.disposed(by: bag)
这里有一个T / H {官方的演示– F ` D 9 D 应用程序包括所有识别器的例子B . b y U ^: ➡️ i8 W } ] S cOS, macOS.
-
RxGes, u 4 2 N : ( 8ture
此外还支h p q a P e C持委托定制:
每个手势识别器都有一g $ !个默认的R` m Q PxGestureRecognizerDelegate。它允许你使用一个策略自定义每个委托方法:
.always
: 对应的委托方法是p W { 8否返回true.never
: 将返回false到相应的委托方法.custom
: 获取将执行的关联1 C 7闭包,以将值返/ # / f T %回给相应的委托方法
以下是可用的策略及其相应的委托方法:
beginPe 6 l ] ^ $ & ~olicy -> gestureRecognizerShouldBegin(:_)
touchReB W @ceptionPolicy -&gg = * y , { ; yt; gestureRecognizer(_:shouldReceive:)
selfFailureRequirementPolicy -> gestureRB S $ecognizer(_:shouldBeRequir# , R 8 gedToFailBy:)
otherFailureRequirementPolicy -> gestureRecQ + F k D h y @ognizer(_:shouldRequireFailureOf:)
simb P I F h g #ultaneousRecognitionPolicy -> gestureRecognizer(_:shoulE L _ T g C JdRecognizeSimultaneouslyWith:)
eventRecognitioa u ` . unAttemptPolicy -> gestureReco } e W dgnizer(_:shouldAttemptToRecognizeWith:) // macOS only
pressReceptionPolicy ->@ P ? z + gestureRecognizer(_:shouldReceive:) // iOS only
这个委托可以在配置包中定制:
view.rx.tapGesture(configuration: { gestureRecognizer, delegate in
d0 u x 3 + s L ?elegate.siz g m O 1 nmultaneousRecognitionPolicy = .always // (default value)
// or
delegate.simultaneousRecognitionPolicy = .never
// or
delegate.simultaneousRecognitionPolicy = .custom { gestureRecognizer, otherGestureRecogniv ` B 7 E bzer in! 6 V
return other6 = j 5 G m 9GestureRecognizer is UIPanGestureRecognizer
}
delegate.otherFailureReq2 T +uirementPolicy = .custom { gestureRecognizer, ot| Z z v U q uherGes9 - $ 6 )tureRecognizer in
return otherGestureRecognizer is UILongPressGestureRecognizer
}
})
默认值可以e Q j e 9 ] L在RxGestureRecognizerDelegated 7 V ( n.swift中找到。
-
RxGesture
次外还支持完全自定义方式:
您还可以用自k G ( C u己的委托替换默认委托,或者删除它。代码如下:
view.rx.tapGesture { [unowned self] gestureRecognizer, delegate in
gestureRecognizer.delegate = nil
// or
gestk ~ $ 0 - PureRecognizer.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
- 源码下载:? v 0 RxOptional
RxOptional适用于Swift选项和“可占用”类型的
RxSwiftk @
扩展。
除另有说明外,所有操作符也可用于驱动程序和信号。
- 可选操作:
filt C 1 } ) a q gerNil的用法:
Observable<String?>
.of("One", nil, "Three")
.filterNil()
// Type is now Observ o 8 P * $ q `able<String&| , t = { h ( igt;
.subscribe { print($0) }
结果打r ; % J S U .印:
next(One)
next(Three)
completed
replac* ( ` deNilWi; Z 4 % g L b f xth 的用法:
Observable<String?>
.of("One", nil, "Three")~ R : ~
.replace] r [ D NilWith("Two")
// Type is now Observable<String>
.subscribe { printi ) ~($0) }
打印结果:
next(Oo j k 6 P P * ,ne)
nextt & +(Two)
next(Three)
completed
errorOnNil 的用法:
注意:在驱动程序上不可用,因为驱动程序不能出错。
默认情况下,rxoptionalerror
.foundnilwhile
eunwrappingoptional
有C 4 ) H错误。
Observable<String?>
.of("One", nil, "Three")
.erY J I A ? {rorOnNil()
// Type is now Observable<String>
./ D R * ? t O Esubscribe { prinr H # [ ` $ 4t($0) }
结果打印:
next(One)
error(Found nS * 6 T c . 8 f 2il while| v { 2 F trying to unwrap type <t # C J;Optional<SI F =tring>>)
ca^ ? % ; f E E ,tchOnNil 的用法:
Observable<String?>
.of("On9 y I b p ^ , Qe", niU s r M [ x k *l,i F g # 5 T "Three")
.c7 Z X j c A O w :atchOnNil {
retD a w E @ ; - curn Observable<String>.just("A String from a new Observable")
}
// Type is non 1 # j 2 w nw Observable<String>[ * y
.subscribg - 2 8 O Xe { print($0)u ( w v I L w }
打印结果:
next(OV K , F 8ne)
next(A String from a new Obsx R U )ervable)
next(Three)
completed
distinctUntilChah % : @nged 的用法:
Observable<u I 8 W k ] z y 6Int?>
.of(5, 6, 6, nil, nil, 3)
.distinctUntilChanged()
.subscribe { print($0) }
打印结果:
next(Optional(5))
next_ * w s - $ l b(Oe F @ Xptional(6))
next(nil)
next(Optional(3))
completed
- 占位操作主要有:
String
Array
Dictionary
Set
目前在Swift协议中不能扩展到符合其他协议。目前,上面列出的类V 3 q u H b s型符合Occupk D 0 S ciable。您还可以使自定义类型符合Ot K 4 K $ : + Nccupiable。
filterEmpty 的用法] 0 T . ?:
Observable<[String]>
.of(["Single Element"], [], ["Two", "Elements"])
.filterEmpty()
.subscribe { print($0) }
打印结果:
next(["Single Element"])
next(["Two", "Elements"])
completed
errorOnEmpty的用法:
在驱动程序f 9 3 0 ]上不可用,因为驱动程序不能出错。
默认情况下,RC { b w g lxOp_ T i ] E 4 . ;tionalError.emptyOccupiable! | z f ? B _ v x
会出现错误。
Observable<[String]>
.of(["Single ElemZ U V O x 4ent"], [], ["Two", "Elements"])
.errorOnEmpty()
.subscr= { ] oibe { print($0) }
打印结果:
next(["Single Element"])
error(Empty occup7 j o Qiable of type <ArrH f $ c I $ (ay<Strie # |ng>>)
catchOnEmpty 的用法:
Observable<[String]>
.of(["Single Element"], [], ["7 Q ? S v 1 |TwP 5 5 qo", "Elements"])
.catchOnEmpty {
return Observable<g x Q S[String]>.just(["Not Empty"])
}
.subscribv 9 l b % ~e { print(B V ? ? ) C 5$0) }
打印结果:
next(["Single Element"])
next(["Not Em& @ -pty"])
nexP X 9 ; 7 e q |t(["Two", "Elements"])
completed
- 安装方式:
C$ p 7 H ? | n 7ocoaPods
RxOptional可以通过CocoaPods获得。要安7 x y & c ? 5 m装它,只需将以下行添加到您的Podfile中:
pod 'RxOptional'
Carthage
将, b – B 4 M此添加到Cartfile
github "Rx: P ? ^ e 3 G 6Swif3 * t C / T q q &tCommux 8 8 n cnity/RxOpt0 c g Y / k P *ional" ~> 4.1.0
$ carthage update
2.2.3.7 RxTheme
- 源码下载: RxTheme
RxTheme基于Rx的主题管理扩展框架
- 安装方式:
Cocoapods
p` q | Ood 'RxTheme', '~Q : G ) q Z G X> 4.0'
Carthage
github "RxSwiftCommunity/RxTheme" ~> 4.0.0
通过RxTheme 你可以这样定义app 的主题服务:
import RxTheme
protocol Theme {
var backgroundColor: UIColor { get }
va J Er textColor: UIColor { get }
}
struct LightTheme: Theme {
let backgroundColor = .white
let textColor = .black
}
struB 4 *ct DarkTheme: Theme {
let backgroundColor = .black
let textColor = .white
}
enW + g p ^ Y ] 2um ThemeTypel Q ) 2 O ] l -: ThemeProvider {
case light, dark
var associatedObject: Theme {
switch self {
case .light:
retu; K H Ern LightTheme()
case .6 ] $ I * * B 8dark:S L $ y x 3
return DarkTheme()
}
}
}
let themeService = Them? p 2 t [eType.service(initia^ n # el: .light)
- 将主题应用到UI
// Bind stream to a singlJ 4 P z R = _e attribute
//Q : K _ In the way, RxTheJ s 2 9 ^me would automatically manage the lifecycle of the binded stream
view.theme.backgroundColorA a q Q ! G = themeSV ` 6 6 5ervice.attrStream { $0.backgroundColor }
// Or bind a bunch of attributes, add them to ax T , w @ j + s disposeBag
themeService.rx
.bi2 f N g U { l * Vnd({ $0.textColor }, to: label1.rx.textColor, label2.rx^ 3 C # e J.textColor)
.bind({ $0.% I i 8 E Y , MbackgroundColor }, to: view.rx.backgroundColor)
.disposeR 7 | I { a Z qd(by: disposeBag)
所有由Th~ | x m ~ hemq $ GeService生成的流都是共享的(1)
- 你可以很轻松的实现换肤,切换主题的功能,只需要一行代码搞定:
themeService.switch(.d{ R o X l #ark)
// When this is trigge6 M R O ered by some signal, you can use:
sov j meSignal.bind(to: themeService.switcher)
此外RxTp ~ Z X h l h OhV Q ^ a o j ] 2 eme还提供了下面的一些API:
// Current theme type
themeService.type
// Current theme attributes
themeSeR ? 7 G Mrvice.atu f ftrs
// Theme type stream
themeService.typeStream
// Theme attributes stream
themeServicel ! $.attrsStream
- 已经实现预设的绑定器有:
CALayer
backgr, ; m h – | eoundColor
borderWidN F 4 l b G S pth
borderColor
shadowColor
CASo O c q | B ? lhapeLaU x { b = pyer:
stb G b { V N w 9 hrokeCo4 K P ( b W V ~lor
fillColor
UIActivityIndicatorView
style
UIY – bBarButtonItem
tintColor
UIButton
titleColor
UILabel
font
textColor
highlightedTextColor
shad} = n r T 3 #ow4 i # # T a k P zColor
UINavigationBar
barStyle
barTintColor
titleTextAttributes
UIPageControl
pageIndicatorTintColor
currentPageIndicatorE e A Z j j ^TintColor
UIProgressVz P 9iew
progressTintColor
trackTintColor
UISearchBar
barStyle
barTintColor
keyboardAppearance
UISlider
thumbTintColor
minimumTrackTintColor
maximumTra= A ^ O g ; I KckTintColor
UISwitch
onTintColor
thumbTintColor
U6 / g ) ! ? 7 tITabBar
barStyle
barTintColor
UITaN f + xbleView
separatorColor
UITAbleViewCell
s0 3 , C Z [electi* r g ( # 0onStyle
UITextField
font
textC: 7 Polor
keyboardAppearance
UITextView
font
textColor
keyboardAppearance
UIToolbar
barStyle
barTintColor
UIView
tintColor
- 你还可以选择自己扩展代码库中的绑定:
因为RxTheme
使用来自RxCocoa
的Binder<T>
,所以RxCocoa
中定义的任何Binder
都可以在这里使用。
这也使得库超级容易在你的代码库中扩展,下面是一个例4 . R ~ ] _子:
exu . Q * ^ Ntension Reactive wh` X @ e Vere Base: UIViI O 3 0 U 1 u *ew {
var bo) W M l 2rderColor: Binder<UIColor?> {
return Binder(s4 p h ] o , .elf.base) { view, color in
view.layer.bU j I 7 .orderColor = color?.cgColor
}
}
}
如果您还想使用sugar view.theme
。边界颜色,你必须写另一个扩展:
exteV 7 U F 6 ~nsion Th] 7 Q e Y vemeProxy where Base: UIView {
var borderCA f , % J l x I &olor: Observable<UIColor?> {
get { return .empty() }
set {
let disposable = newValue
.takeUntil(base.rx.deallocating)
.obO * ) K y ? $ serveOn(MainScheduler.instance)
.bind(to: base.rx.borderColor)
hold(disposable, for: "8 y gborderColor")
}
}
}
2.2.3.8 RxAnim? v 9 y W P m tated
- 源码下载: RxAnimated
2.2.4 图像处理w u X库
2.2.4.1 Kingfisher
2.2.5 资源文件管理库
2.2.5.1 R.swift
- 源码下载:R.swift
2.2.5.2 SwiftLint
- 源码下载L , g g 9 – [ 9 1: SwiftLini T t T a W A tt
2.2.6 秘钥管理库
2.2.6.1 KeychainAccess
- 源码下载: KeychainAccess
2.2.7 自动布局库
2.2.7.1 SnapKit
- 源码下载: SnapKit
2.2.8 UI相关库
2.2.8.1 NVActivityIndicatorView
- 源码下载:NVActivityIndicatorView
2.2.C 8 h M q I8.2 ImageSlidershow/Kingfisher
- 源码下载: ImageSlidershow/K1 | m s f i vingfisher
2.2.8.3 DZNE, ) S D SmptyDataSet
- 源码下t g e K z / v载: DZf A + = # j PNEmptyDataSet
2.2.8.4 Hero
- 源码下载:Hero
2.2.8.5 Localize-Swift
- 源码下载: Localize-Swift
2.2.8.6 RAMAnimatedTabBarController
- 源码下载:RAMAnimatedTabBarController
2.2.8.7 AcknowList
- 源码下载: AcknowList
2.2.8.8 KafkaRefresh
- 源码下载:KafkaRefresh
2.2.8.9 WhatsNewKit
- 源码下载: WhatsNewKit
2.2.+ l [ U . M ! Z8.10 Highlightr
- 源码下载: Highlightr
2.2.8.11 DropDown
- 源码下载:DropDown
2.2.8.12 Toast-Swifv = Z i M Wt
- 源码下载:To[ [ 3 W Z W Gast-Swift
2.2.8.13 HMSegmentedControl
- 源码下载: HMSegmentedControl
2.2.8.14 FloatingPanel
- 源码下载: FloatingPanel
2.2.8.1{ ? m G5 MessageKit
- 源码下载: MessageKit
2.2.8.16 MultiProgressView
- 源码下载: MultiProgressView
2.2.8.17 IQKeyboardMank G . W 4 ^ g / agerSwift
- 源码下载:IQKeyboardManagerSwift
2.2.9 日志管理库
2.2.9.1 CocoaLumberjm M H 9 Tack/Swift
- 源码下载:CocoaLumberjack/Swift
2.2.10 数据埋点库
2.2.10.1 Umbrella
- 源码下载: Umbrella
2.2.10.2 Umbrella/Mixpanel
2.2.10.3 Umbrella/Firebase
2.2.10.4 Mixpanel
- 源码下载:Mixpanel
2.2.10.5 Firebace/Analytics
2.2n 0 ` Q.11 广告工具点2 H e库
2.2.11.1 Firebase/AdMi R 4 ~ 7ob
2.2.11.2 Google-Mobile-Ads-SDK
2.2.12 性能优化相关库
2.2.12.1 Fabric
2.2.12.2 CrashlyticW $ 9 ~ % / z Ys
2.2.13 其他工具类库
2.2.13.1 Fm x K , I sLEX
- 源码下载: FLEX
2.2.13.2 SwifterSwift
- 源码下载: Swig I _ UfterSwift
2.2.13.3 BonMotR v X | z # x +
- 源码下载:BonMot
2.2.13.4 DateToolsSwift
- 源码下载:DateToolsSp A b g bwift
2.2.1; [ 53.5 SwiftDate
- 源码下载: SwiftDate
3. SwiftHub项目采用的x @ o架构分析
参考:www.jianshu.com/p/fb63ca356…