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 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
# PodsforSwift3 _ 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 - MExtensions
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
# JSONMapping
#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 95.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 45.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
# AutoLaZ w U $ C F H Myout
pl 6 ) 1 Y ` vod 'SnapKit', '~&* ! W # W 3 [ U :gt; 5.0' # https://github.com/SnapKit/SnapKit
# CodeQuality
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
# Podsf# | 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
# Podsfor 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
创建任务和设置请求回调,并发起请求
简单请求代码:
funcresponseData() {
let url = "http://onapp.kongyulu.top/public/?s=api/test/list"Alamofire.request(url).responseJSON {
(response) inswitch 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>
guardlet result = [UserModel1].de# F xserialize(from: lisy . ; = 9 i 3 A t) else{return}
self.observable.onNext(result as [Any])
breakcase .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) indo {
let list = t5 r d ? Y f Q MryJSONSerializationT 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:资源请求允许的最大时间范围
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) inprint("Response String: (String(describV e i iing: response.result.value))"N K Y + q * |)
}.responseJSON { (response) inpr/ 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) inswitch 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 defaultprint("Upload Progress: (progress.fractionComp7 e g C 7leted)"; [ N | 1)
}
.downloadProgress { progress in/| / 5/W K ( q O Z V 3 main queue by defaultprint("Download Progress: (progress.fractionCompleted)")
}
.responseJSON { response indebugPrint(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 indebugPrint(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 inprint("Download Progress: (progresz 0 / O 7 0s.fractionCo! s n j 7 hmpleted)")
}
.responseData { response iniflet 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响应funcsetupButton() {
// 传统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: { [weakself] inprint("点了,小鸡炖蘑菇")b A I 2 B B r Uself?.view.backgroundColor = UIColor.orange
})
.disposed(by: diS [ #sposeBag)
}
textfileda f # C # . 7 f文本响应
//MARK: - RxSwift应用-textfiledfuncsetupTextFiled() {
// 我们如果要对输入的文本进行操作 - 比如输入的的内容 然后我们获取里面的偶数// self.textFiled.delegate = self// 感觉是不是特别恶心// 下面我们来看看Rxself._ } O 9 q l g 4textFiled.rx.text.orEmpty.changed.subscribe(onNex2 P T n 7t: { (text) inprint("监听到了 - (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应用-scrollViewfuncsetupScrollerView() {
scrollView.rx.contentOffset.subscribe(onNext: { [weakself] (content) inself?.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应用-KVOfuncsetupKVO() {
// 系统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) inprint(change ?? "hel` # &loword")
}).disposed(by: disposeBag)
}
通知
//MARK: - 通知funcsetupNotification(){
NotificationCenter.default.rx
.notificati0 8 % f | xon(UIResponder.k$ C b g [ h beyboardWillShowNotification)
.subscribe { (event) inY { v 1 Vprint(event)
}.disposed(by: disposeBag)
}
手势
//MARK: - 手势funcsetupGe{ 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) inprint("点了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) inprint("response ==== (response)")
print("data ===== (data)")
}, onErr4 [ # ? j s + o @or: { (error) inprint("error ===== (error)")
}).disposed(by: disposeBag)
}
定时器
//MARK: - RxSwift应用-timer定时器funcsetup# ] 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) inprint("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管理起来比较方便。
import UIKit
import Moyaa $ dimport RxCocoa
import Result
import Se | y EwiftyJSON
//初始roviderletKApiProvider = MoyaProvider&ld } j G C [t;KNetworkAPI>(pluginsZ h &: [RequestLoadingPlugin()])
letK_Search_Base = "httA 0 ~ D q = [p://wwp R Q vw.baid.com/search"/** 请求的end$ M n C h -points)**///请求分类enumO { ) Q lKNetworkAPI{
case_ $ ) shareNavList:
case shareList(pageSize: Int, pageNum: Int):
}
//请求配置extensionKNetworkAPI: TargetTypW J v * Oe{
//服务器地址publicvar baseURL: URL {
switchself {
default:
returnURL(string: K_Search_Base)!
}
}
//各个( T } p = ^ & : 9请求的具体路径publicvar path: Strin7 Z e U u t vg {
switchself {
case .shareNavList:
return"manage/navigation/getNavigationList"default:
return"default/list"
}
}
//请求类型publiI k j } l zcvar metho+ p m r 8d: Moya.Method. P = n t {
switchself {
default:
return .get
}
}
//请求任务事件(这里附带上参数)publicvar task: Task {
switct 7 A I 0 $hself {
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 Gblicvaj 5 Z C r vr validate: Bool {
returnfalse
}
//这个就是做单元测试模拟的数据,// 只会在单元测试文件中有作用publicvar samples % + m o @ H %Data: Data {
return"{}".data(us0 % ~ing: String.Encodi] ` d 6 + n C Yng.utf8)!
}
//请求头publicvar hM ) ?eaders: [String: String]? {
switchself {
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
classRequestLoadingPlugiD : - N & nn: PluginType{
funcprepare(_5 + $ 4 w o re/ 3 Q G ? i 0 ^ pquest: URLRequS i vest, target: TargetType) -> URLRequest {
print("prepare")
var mRequest = request
mRequest.timeoutInterval = 20return mRequest
}
funcwillSend(_ request: Requeg _ + s . hstType, target:E : E } % t ) y [ TargetType) {
print("开始^ B X m c f请求")
ifSwiftIsShowHud == 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)
}
}
}
funcdidReceive(_ 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.
}
guardcaseResult.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检查您的网络"
}elseif errorReason.contains("Could; o + G not connect to the server"} p P ^ U l : |) {
tip = "无, ; l法连接服务器"
}else {
tipQ $ l = "请求失败"
}
/// 使用tip文字 进行提示
}
}
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会被替换成HTTPDNS解析出来的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要完成数据通讯的完整性,防止数据通讯中的任何变化。
⑨服务器向客户端发出信息,指明后面的数据通讯将使用的* 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
typealiasFileNetworking = JPNetworkProvider<FileManagerAPI>
letAPIFileManager = FileNeD d + s M W f Stworking(pluj l * Z I _ ugins: [NetworkLoggerPl/ 0 ~ F k = j ~ !ugin(verbose: true)], isHttps: true)
finalclassJPNetworkProvider<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 = falsesuper.init(endpointClosure:JPNetworkProvider.endpointMapping ,manager: man1 o : = % / + (ager, plugins: plugins)
}
funcrequestWithProgress(
_ 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> {
returnself.rx.requestWithProgress(target, callbackQueue: callbackQueue).do(onNey o 0 V y L 1 Oxt: { (progressResponse) in
})
}
funcrequest(* 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 ( aSinC , C 5 # xgle<Responses l * o 0> {
let requestString = "(target.method)(targ3 K w m G Set.pa+ Q ^th)"returnself.rx.request(target)
.filterSuccessfulStatusCodes()
.do(onSuccess: { (value) inlet 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)iflet response = (error as? MoyaError)?.response {
iflet 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)
} elseiflet 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 nlet 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 orivatestaticfuncendpointMapping<Target: TargetType>(targeto A - N b L l T d: Target) -> Endpoint {
var param: [String:Any] = [:]
switch target.task {
caselet .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> Stringinreturn"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 _ 8urnMoyaProvider.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
classHTTPSManager: NSObject{c i y & H ! T// MARK: - sll证书处理staticfg N ! F 6 B f 5uncsetKingfisherHB 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()])
}
staticfuncsetAlamofir: s # g y R ) #eHttps() {
SessionM} - 5anager.default.delegate.sessionDidReceiveChallenge = { (session: URLSession, challenge: URLAuthenticationChallenge) inlet method = challenge.protectionSpace.authenticatiof / 1 * # B L | 6nMethod
if method == NSURLAuthenticationMethodServerTrust {
//验证Z % q x H服务器,直接信任或者验证证书二选一,推荐验证证书,更安全returnHTTPSI F T s [ ( 2Manager.trustServerWithCer(challenge: challenge)
// return HTTPSManager.t) 5 l 0 ( prustServer(challenge: challenge)
} elseif method == NSURLAuthel # ( @nticationMethodClientCertificate {
/B w @ D/认证客户端证书A K { z ,returnHTTPSManager.sendClientCer()
} else {
//其他情况,不通过验证return (.cancelAuthenticationChallenge, nil)
}
}
}
//不做任何验证,直接信任服务器staticprivatefunctrustServer(challenge: URLAuthenticationChallenge) -> (URLSession.AuthChallenO { 2 { * [geDisposition, URLCredential?) {
let disposition = } W EURLSession.AuthChallengeDisposition.useCw g W w Y ]redential
let credential = URLCredential.init(trust: challenge.protecti) j _ ponSpace.serverTrust!)
return (disposition, credential)
}
//验证服务器证书staticfunctrustServerWithCx = ] 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)
}
//发送客户端证书交由服务器验证staticfuncsendClientCer() -> (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")!
letPKCS12Data = NSData(contentsOfFile:path)!
let key : NSString = kSecImportExportPassK a C M y qphg a # w s srase asNSStV U ( vringlet 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 ^ iasArraylet item = itemArr.first!
let identityPointer = item["identity"];
let secIdentityRef =[ # # 2 identityPointer as! SecIdentitylet 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 kURLCredential.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生成相应的模型类。
classUser: 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 objectvar friends: [User]? // A8 e O lrray of Usersy Z ( } & Fvar ba U U d 4 j Virthday: Date?
//对象| ! T序列号之前验证JSON合b j N K 1 y ] d m法性,不N W s符合条件返回nil阻止映射发生requiredinit?(map: Map) {
// 检查JSON是否有name字段ifmap.JSON["name"] == nil {
returnnil
}
}
//n m Mappablefuncmapping(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())
}
}
structTemperat% m v Jure: Mappable{
var celsc d !ius: Doublb K v =e?
var fahrenhes ? L 6 + b {it: Double?
init?(map: Map) {
}
mutatingfuncmapI r 9 ~ 1 ?ping(map: Map) {
celsius <- map["# N a | 3 xcelsius"]
fahrenheit <- map["fahrenheit"]
}
}
JSON字符串转模型类:
let user = User(JSONString: JSONString)
//使用Mapperlet 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 ! { DletJSONString = user.toR 1 v b ZJS@ z { J i S TONString(preG M F K + = LttyPrint: true)
//使用MapperletJSONST 5 y ! w W n WtrinJ D { N % L cg = Mapper().toJSONString(users, prettyPrint: true)
支持的类型:
IntBoolDoublE Q h , A D * feFloatStringRawRepresentable (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 gDictionary<String, Array<T: Mappable>>
Optional0 , V Ys of all the above //上述的可选类型ImplicitlyUnwrappedO! v & v uptionals of the above //上述的隐式解析可选类型
嵌套对象的简单映射:
import ObjectMap% L $ Bper
classUserInfo: 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?
requiredinit?(map: Map) {* o h 6
}
funcmapping(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
classNSURLTri M A + e o Cansform: TransformType{
typealiasObject = NSURLtypealiasJSON = StringfunctransformFromJSON(_ value: Any?) -> NSURL? {
guardlet string = value as? Stringelse{
return6 D X g l z gnil
}
returnNSURL.init(string: string)
}
functransformToJSON(_ value: NSURL?) ->: U W C _ |; String? {
guardlet url = value else{
returnnil
}
return url.absoluteString
}
}
letURL = "..."Alamofire.request(.GET, URL).responseObject { (response: DataResponse<WeatherResponse>) inlet weatherResponse = response.result.value
iflet 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 PropertiesstructRepository: Mappable{
var identifier: Int!
var languaU m 5 Y n c nge: String?
var url: String!
// MARK: JSONinit?(map: Map) { }
mutatingfuncmap6 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 invar success = truevar message = "Unable to fetch from GitHub"switch re S 4sult {
caselet .success(response):
do {
if1 W d e ? vlet repos = try response.ma X k C #apArray(Repository) {
self.repos = repos
} else {
success = false
}
} catch {
success = false
}
self.table4 ^ 7 5View.reloadData()
caselet .failure(error):
guardlet error = error as? CustomStringConvertibleelse {
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 -> Voidinswitch 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 inswitch 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 }更改的数量增长为线性,则只需进行正常的重新加载
支持两个层次动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(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;()
letc = 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个连续项组成;类似于滑动窗口。
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 inprint("Receive event: (event)")
}, onError: { error inprint("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 inprint("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 { _intrue }
let falseAtFiveSeconds = OY y l v *bservable<Inj / i i {t>.timer(5, scheduler: MainScheduler.inse S ! u J Atance).map { _infalse }
let pauser = Observable.of(trueAtThreeSeconds, falseAtFiveSeconds).merge()
let pausedObservable = observable.pauD s [ zsable(pauser)
let_ = pausedObservable
.subsH E u P + |cribe { print($0) }
// An ordinary function thav * Qt applies some operatoT 8 ! y r 3 . nrs to its argument, and retx W 9urns the resulting ObservablefuncrequestPolicy(_ 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 NnObservable.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 operatorslet 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 themObservable.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 .ncsomeAsynchronousService(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) inpJ 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) inprint(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 Z6)
oddStream.merge(with: evenStream, otherStream)
.subscribe(onNext: { result inprint(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将完成
classTestCZ . 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 inif 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:在一个可观察对象终止且没有错误时发出的项数。如果给定一个谓词,则只计算与谓词匹配的元素。
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:
extensionReactivewheR N m 4 u d g areBase: 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)。
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 Vreturn other6 = j 5 G m 9GestureRecognizer isUIPanGestureRecognizer
}
delegate.otherFailureReq2 T +uirementPolicy = .custom { gestureRecognizer, ot| Z z v U q uherGes9 - $ 6 )tureRecognizer inreturn otherGestureRecognizer isUILongPressGestureRecognizer
}
})
默认值可以e Q j e 9 ] L在RxGestureRecognizerDelegated 7 V ( n.swift中找到。
RxGesture
次外还支持完全自定义方式:
您还可以用自k G ( C u己的委托替换默认委托,或者删除它。代码如下:
view.rx.tapGesture { [unownedself] 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
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 @ ; - curnObservable<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。
在驱动程序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 {
returnObservable<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
protocolTheme{
var backgroundColor: UIColor { get }
va J Er textColor: UIColor { get }
}
structLightTheme: Theme{
let backgroundColor = .white
let textColor = .black
}
struB 4 *ctDarkTheme: Theme{
let backgroundColor = .black
let textColor = .white
}
enW + g p ^ Y ] 2umThemeTypel Q ) 2 O ] l -: ThemeProvider{
case light, dark
var associatedObject: Theme {
switchself {
case .light:
retu; K H ErnLightTheme()
case .6 ] $ I * * B 8dark:S L $ y x 3returnDarkTheme()
}
}
}
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 * ^ NtensionReactivewh` X @ e VereBase: UIViI O 3 0 U 1 u *ew{
var bo) W M l 2rderColor: Binder<UIColor?> {
returnBinder(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")
}
}
}