开源项目剖析(SwiftHub)Rxswift + MVVM + Moya 架构剖析(一)第三方结构运用 (上)

开源项目剖析(SwiftHub)Rxswift + MVVM + Moya9 ( , 4 ? L I 架构剖析(二)第三方结构运用 (中` W y ! n)

@[TOC]

2. SwiftHub运用的Q / G D c y q O B第三方库

这篇博客是上篇博客“开源项8 L N U u S目剖d # | / t c 0 X q析(j m Y 9 { #SwiftHub)Rxswift + MVVM + Moya 架构剖析(一)第三方结z $ T # )构运( f ? % I J用” 的续集,因为篇幅进程,拆成几部分了。F # # ( x u } u j

先回忆一下第三方结构图:

SwiftHub项目用的第三方库

2.1 Rxswift 家族库

2.1.1 RN W nxAnimated

  • 源码下s k Q载: RxAnimated
RxAnimated的星星

RxAnimated
为RxCocoa的绑定供给了动画接口

它供给了一6 n Y q , O G V些预界说的动画绑定,并供给了一种灵活的机制,让您能够增加自己的预界说动画,并在与RxCocoa绑定时运用它们。

2.1.1.1 RxAnimated根本动画运用

  • RxAnimatet l C Y – 5 t sd供给的内置动画:

当与RxCocoa绑定值时,你能够这样写:

textObserW 2 A { 2vable
.bind(to: labelFlip.rx.te/ 4 X a Y } / W hxt)
label更新动画

每逢observable发出一个E ] H新的字符串值时,它都会更新标签的文本。但这是突然发生的,没有任何过渡。运用RxAnimated,你能够运用animated扩展来绑定值和动画,就像这样:

textObservable
.bind(animated: labelFlip.rx.a9 1 bnimated.flip(.top, duration: 0.33).text)
labelFlip动画

“不同之处在于”您运用bind(animated:)而不是bind(to:),然后您插入animated.flip(.top, duration: 0.33)(或其他供给或自界说动画办法之一)之间的rx和特色接收器你想运用。

2.1.1.2 RxAnimated根本动画列表

  • 内置动画类型列表:
UIView.rx.anima! G % 4 q K m 3 eted...isHidden
UIView.rx.animatedc * u 3 X...alpha
UILabm o V @ g ^ I 6 xel.rx.animated...text
UILabel.rx.animated...atr d b z P ~ d vtributedText
UIControl^ u e ^.rx.animated...is2 L ^Enabled
UIControl.rx.animated...isSelected
UIButton.rx.animated...title
UIButton.rx.animated...image
UIButton.rx.as [ B hnimated...backgroua k : M P 5 + V ndImage
UIImageView.rx.aS s [ F { + V F `nimated...image
NSLayoutConstraint.rx.animated...constant
NSLayoutConstraint.rx.animated...isActive
  • 内置动画列表:
UIView.rx.animated.fade(duration: T. ? G M wimeInterval)
UIView.rx.animated.flip(c x ; Y |FlipDirection, duration: TimeInterval)
UIView.rx.animated.tick(FlipDirection, duration: TimeInterval)
UIView.rx.animated.animation(duration: TimeInterval,1 . * * B animations: ()->Void)
NSLayoutConstraint.rx.animated.layout(duration: TimeInterval)

2.1.1.2 RxAnimated自界说动画

您能够轻松地增加自界说绑定动画来匹配应用程序的视觉风格。

  • 第一步:(可2 n S ] M A / [选)假如你正在激k 3 , d活一个没有动态绑定的新绑定接收器(例如UIImageView.rx)。形象,UILabel.rx。文本和更多的已经包括在内,但你需求另一个特色)
// This is your class `UILabel`
extension AnimatedSink where Base: UILabel {
// This is your propert9 P Yy name `text` and value type `String`
public var text: Binder<String> {
let ani? A , m Qmation = self.type!
return Binder(self.base) { label, text in
animation.animate(view: laT o  Lbel, block: {
guard let label = l, , i ^abel as? UILabel else { return }
// Here you updatZ  v N i k J Re the property
label.text = text
})
}
}
}
  • 第二步:增加新的动画办法
// This is your class `UIView`
extension AnimatedSink where Base: UIView {
// This is your animation name `tick`
public func tick(_ direction: FlipDirection = .right, duratiog  2 b `n: TimeInterval) -&% D I 5 y : ] Wgt; AnimatedSink<Base> {
// use one of the animation types and provide `setup` and `animation` blocks
let type = AnimationType<Base>(type: RxAnimationType.spring(damping: 0.33, velocity: 0), durX O d 0 Yation: duration, setup: { viewJ K b . . G in
view.alpha = 0
view.transform = CGAff2 x @ S H U iineTransform(rotaD q j N U 9 x N +tionAnglo h 6 ae: direction8 # * 2 # == .right ?  -0.3 : 0.3)
}, animations: { view in
view.L F 3 b ~ Talphe g s ]a = 1
view.transform = CGAffineTransform.identity
})
//return AnimatedSL W  e 8 7 3ink
return AnimatedSink<Base>(base: self.base, type: type)
}
}
  • 第三步:现在能够运用新的动画绑定订阅了。通常是这样绑定UIImageView.rx.image 如下
imageObservable
.bind(to: imageView.rx.image)

成果是非动画绑定的作用:

一般点击动画

假如你运用你的新自界说动画绑定像这样:

imageObservv ( 2 Q % 7 f =able
.bind(to: imageView.rx.animated.tick(.right, duration: 0.33)/ w x ? W =.image)

修改后的作7 & # { h t C J ]用是这样的:

修改后的作用

假如你在UILabel上_ ` | F 4 Y A U运用相同的动画:

textObservable
.bind(to: labelCustom.rxe E Y   [ U.animated.tick(.left, duration:; M B y b % d 0.75).text)

作用如下:

Label运用自界说动画作用

2.1.1.2 RxAnimated 装置

  • RxAne $ %imated依赖于RxSwift 5+。 O q #
  • RxAnimated能够经过CocoaPods取得。要装置它,只需增加以下行到您的Podfile:
pod "RxAnimated"

2.2 图画处理库

2.2.1 Kingfisher

  • Kingfisher源码下载

    Kingfisher的星星

    Kingf= G P L ;isher是一个强壮的,纯粹的swift库下载和缓存图画从; t Z Y U F X A o网上。它为你供给了一个时机,运用一个纯粹的快速的办y Y w法来处理你的下一个应$ ` L p用程序中的长途图画。

2.2.1.1 Kingfisher特色

  • 异步图画下载和缓存。
  • 从基于url会话的网络或本地供给的数据加载图画。
  • 供给了有用的图画处理器W { Y和过滤器。
  • 用于内存和磁盘的多层混合缓存。
  • 对缓存行为的精细操控。可自界说的过期日期和巨细限制。
  • 可撤销的下载和主动重用以前下载的内容Q 2 l 7 @ z z i,以进步性能。
  • 独立的T – ` 6 # ~ t组件。根据需求别离运用下载器、缓存体系和图画处理r : e @器。
  • 预抓取图片并从缓存中显现,以提高你的应用程序。
  • UIImagn ! l N 2eView, NSImageView, NSButtonUIButton的视图扩展来直接从URL设置图画。
  • 内置过渡j n N M动画时,设置图画。
  • 加载图画时可自界说的占位符和指示器。
  • 易于扩展的图画处理和图画格式。
  • SwiftUI支撑。

2.2.1.2 Kingfisher简略运用

2.2.1.2.1 根本用法
  • 最简略的用例是运用UIImagS c 9 4eView扩展将图画设置为图画视图:
let url = URL(string: "https://example.com/image.png")
image6 Q h & R _ z ! SView.kf.setImage(with: url)

Kingfisher将从url下载图画,将其发送到内存缓存和磁盘缓存,并在imageViewU B d T显现。当您稍后运用相同的URLD + 5 x b W Y置时,图画将从缓存中检索并当即显现。

假如你运用SwiftUI也能够这样写:

import KingfisherSwiftUI
var body: some View {
KFImage(URL(string: "https://exampY D * O (le.com/image.png")!)
}

此外,KU e ningfisher还供给了一些高阶用i ^ o r j b ] ! y法,用于处理杂乱的问题,有了这些强壮的选项,您能够用简略的办法用Kingfih 9 ysher完结困难的任务。
例如,下面的代码:

let url = URL(string: "https://example.com/high_resolution_im^ f Dage.png")
let processor = DownsamplingImageProcessor(size: imageView.bounds.size)
>> RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.iT ~ % na  # @ P JdicatoW I % B U ; 3 arType = .activity
imageView.kf.so m ? etImage(
with: url,
placeholder: UIImage(named: "placeholderImage+ T 2 8 1 [ y * ;"),
options: [
.processor(processor),
.scaleFactor(UIScreen.main+ & D R Z A 5 o.scale),
.transition(.fade(1)),
.cacheOrigina) U d 0 9 d TlImage
])
{
result in
switch res# I T U Iult {
case .success(let value):
print("Task done/ { ? for: (value.source.url?.absoluteString ?? "")")
cas; ? 9 : Le .failure(let error):
print("Job failed: (error.localizedDescription)")
}
}

上面代码做了这些操作:

  1. 下载高分辨率图画。
  2. 向下采样以匹配图画视图的巨细。
  3. 使它在给定的半径内成为一, 8 [ % O T m个圆角。
  4. 下载时显现体系指示符和占位符图画。
  5. 准备好后,它会用“渐入淡出& E 9”作用使小的缩略图发生动画作用。
  6. 原始的大图也i 3 c被缓存到磁盘供今后运f p G X i # J + s用,以防# R . 4止在详细U 9 [ e 1 ? 2 *视图中再次下载它。
  7. 当任务完结时,无论是成功还是失败,都会打; , } e 7 a l Y m印操控台日志。
2.2.1.2.2 铲除缓存
func clearCache` q r { : V 1 + g() {
KingfisherManager.shared.cache.cla c ( Y a F `earMemoryCache()
KingfisherManager.shared.cache.clearDiskCache()
}
2.2.1.2.3 下载图片增| V @ L N 8 加UI显现
  • 加载图片显现进展
//显现菊花
imageView.kf.indicatorType = .activity
imO c {ageView.kf.setImage(with: url, placeh+ f h K O k Y 7 /o@ p A ` , Y Dlder: nil, options: [.traj ( :nsition(ImageTransition.fade(1))], progressBlock: { (receviveeSize, totalSize) in
print("(receviveeSiD % Jze)/(totalSize)")
}) { (image, error, cacheType, imageURL) in
print("Finished")
/! : 3 * o h J T/ 加载完结的回调
// image: Imagc i  fe? `nil` means failed
// error: NSError? non-`nil` means faE _ } Giled
// cacheType: CacheType
//            .none - Just downloaded
//            .mem` C % ?ory - Got from memory cache
//            .disk - Got from disk cache
// imageUrl: URL of the image
}
  • 下载进程中 设置菊– l R $ v R }花样式
imz o HageView.kf.M t # [ 8indicatorType = .activity
imageView.kf.setImage(with: url)
//运用自己的gif图片作为下载指示器
let p= & ^ E # d 4 3ath = Bundle.main.N F V b y vpath(forResource: "H @ b b X  Dloader", ofType: "gif")!
let data = try! Data(contentsOf: URL(fileURLWithPath: path)) imageView.kf.indicatorType = .image(imag~ [ M x SeData: data)
imageView.kf.setImage(with: url)
  • 订制指示器view
structU ^ 9 D v KYLIndicator: Indicator6 B j W x g | {
let view: UIView = UIView()
func startAnimati! d 5 Q B + c YngView() {
view.x 8 w O 9 / ZisHidden = false
}
func st@ l i BopAnimatingView() {
view.isHidden = true
}
init() {
view.backgroundColor = .red
}
}
let indiE u ? } ! I T zcator = KYLIndicator()
imageView.kf.indicatorType = .custom(indicator: indicator)c 0 Y % ; - &
  • 图片下载完结后,设置过度作用,淡入作用
imageView.kf.setImage8 r 2 K g : C r(with: url, options: [3 t @ 7 e g ` W S.transition(.fade(0.2))])
  • 在显现和: z L H [ # 2 ;缓存之前将下载的图画转换成圆角
let processor = RoundCo5 X hrnerImageProcessor(cornerRadius: 20)
imageView.kf.setImage(with: url, placeholder: nil, options: [.processor(processor)])
  • 对Button增加图片
        let uiButton: UIButton = UIButton()
uiButton.k[ / 6 S Q g c U %f.setImagf y / ^ v M `e(with: url, for: .normal, placeholder: nil, options: nil, progressBlock: nil, completionHandler: nil)
uiButton.kf.setBackgroum 6 Y J = &ndImage(with: url, for: .normal, placed Y % Y 3holderX M X U A C N 4 V: nil, options: nil, progressBlock: nil, completionHandler: nil)

2.2.1.3 Kingfisher高级设置

2.2.1.3.18 X ; 设置自界说缓存参数
  • 设置磁盘缓存巨细(默许是50MB)
// 设置磁盘缓存巨细
// Default value is 0, which means no limit.
// 50 MB
ImageCache.default.maxDiskCacheSize = 50 * 1024 * 1024
  • 设置缓存过期时刻(默许是z F g r Q3天)
        /m G u U p/ 设置缓存过期时刻
// Default value is 60 * 60 * 24 * 7, which means 1 week.
// 3 days
ImageCache.default.maxCachePeriodInSecond = 60 * 60 * 24 * 3
  • 设置超时时刻(默许是1) x x R5秒)
// Default value is 15.
// 30 second
ImageDownloader.default3 Q 2 0 * m.downloadTimeout = 30.0

其他设置相关

// 设置磁盘缓存巨细
// Default v8 g F 3 m ]alue is 0, which means no limit.
// 50 MB
ImageCache.default.maxDiskCacheSize = 50 * 1024 * 1024
// 获取缓存磁盘运用巨细
ImageCache.default.calculat m e i ; a AeDiskCacheSize { size in
print("Used d0 M S . ] ? e Y jisk size by bytes: (size)")
}
// 设置缓存过期时刻
// Default value is/ h o 8 [ 60 * 60 * 24 * 7, which means 1 wee= ( A ` U @ tk.
// 3 days
ImageCache.default.maxCa$ H ^ u T Z `chePeriodInSecond = 60 * 60 * 24 * 3
// 设置超时时刻
// Default value is 15.
// 30 second
ImageDownloader.default.downloadTimeout = 30.0
// Clear cache manually
// Clear mew 2 _ f ; t xmory cache right away.
cache.clearMemoryCache()
// Clear disk cache. This is an async operation.
cache.clearDiskCache()
// Clean expired or size exceeded disk cache. This is an async operation.
cache.cleanExpiredDisW 4 _ WkCache()
2.2.1.3.2 自界说用法
  • 越过缓存,强制重新下载:
imageView.kf.setImage(with: url, options: [.for9 ) = 4 N o D 4 wceRefresh])
  • 运用自界说key缓存,而不是用url
let resource = ImageResource(downloL K padURL: url!, cacheK$ N Q g l Ley: "kyl_cache_key")
imageView.kf.setImage(with: resource)
  • 缓存和下载分开运用:

Kingfisher 主要由两部分组成,ImageDownloader用于办理下载;ImageCach0 E P f z Z x 9 re用于办理缓存,你能够独自运用其间一个.

//运用ImageDownloader下载图片
ImageDownloader.default.downloadImage(with: url!? C W p y ? L, options: [], progressBlock: nil) { (image, error, url, data) in
print("Downloaded Image: (image)")
}
// 运用ImageCache缓存图片
let image: UIImage = UIImage(named: "xx.l D dpng")!
ImageCache.dex k 3 E Ufault.store(image, forKey: "key_for_image")
// Remove a cN W Y Q 2 1 1 Cached image
// From both memory and disk
ImageCache.default.re m kemoveImage(forKey: "key_for_image")
// Only from memory
ImageCache.default.removeImage(forKey: "key_for_3 V , = Z ` O aimage",fromDisk: fd h b v T calse)
  • 运用自界说的Downloadercache替代默许的
        let kyldK  I e R B e 2ownloader = ImageDownloader(name: "kongT x zyulu_P F E d WimaZ K ~ g $ _ge_downloader")
kyldP & Xownloade8 d ] Z r.downloadTimeout = 150.0
let caP h ) ~che =@ 7 n ImageCache9 j X :(name: "kyl_longer_cache")
cache.maxDiskCachp ! [ @ - JeSize = 60 * 60 * 24 * 30
imageView.kf.p 4 3 F 3 d H ssetImage(with: url, options: [.downloader(kyldownloader), .targetCache(cache)])
// 撤销下载u $ 2 ! , G # _
imageView.kf.cancelDownloadTask()
  • 运用自界说的缓存途径:
            // MARK:- 下载图片
imageView.kf.indicatorType = .activity
let cachePath =  ImageCache.default.cachePath(forKey: PhotoConfig.init().cachePath)
guard let patW e U Nh = (try? ImageCache.init(name: "cameraPath", cacheDirectoryURL: URL(fileURLWithPath: cachePath))) ?? nil el# j b  V [ , W /se { return  }
imageView.kf.setImage(with: URL(string: smallUrK + A 6 i r D :lStr), placeholder:UIImage(named: "PhotoRectangle") , options: [.targetCache(path)], progressBlock: { (receivedData, totolData) in
// 这儿用进展条或许制作view都能够,然后根据 perc^ F Z i f V ventage% 表明进展就行了
//let percentage = (Fli m A v m 1 F Ioat(receivedData) / Float(toL & H ] N atolData)) *b m b 100.0
//print("downloading progress is: (percentage)%")
}) { result in
//                switch result {
//                    
//W l u * x j ` $                 case .success(let imageResult):
//                    print(imageResult)
//                
//                caB z c E * $ j Rse .M I K ) = . {failure(let aError):
//                    print(aError)
//                }
}
  • 预先获取要显现的图片,需求显现时在直接增加
        let urls = ["http://l ! : 0 U - i Jwww.baidu.com/imagS + J |e1.jpg",T ( k ! "hh ) U tttp://www.baidu.com/image2.jpg"]
.map { URLH P y X f { 3 L(string: $0)! }
let prefetcher = ImagePrefetcher(urls: urls) {
skippedResources] ) H y r C F &, failedResources, completedResources in
print("These resources are prefetched: (complX & Q w FetedResouc 3 i # : = + V srces)")
}
prefetcher.start()
// Later when you need to display these images:
imageView.kf.setImage(with: urls[0])
anotherImageViewR Y T _ 2  . U d.kf.setImage(with: urls[O ? T p U e1])

2.2.1.3 Kingfisher HTTPS 图片下载,证书信赖,自签名证书信赖

  • 开发中,咱们或许服务器用的是HTTPS的办法,这个时候假如服务器端是运用的证书颁布组织的证书,咱们下载图片不需求特殊处理,就+ 6 i ( L I能够下载到图} t 5 c U u t 1 d片。可是,假如服务器运用的不是认证组织认证的证书,而是运用自签名证书,运用Kingfisher 下载图片需求做一下证书认证处理。^ { V

或许有些朋友不太熟悉HTTPS握手的进程,要了解证书认证机制,有必H T Q } A Z要了解一r a f $ N V V k下HTTPS握手进程:

发送HTTPS恳求首先要进行SSL/TLS握手,握手进程大致如下:

  1. 客户端建议握手恳求,携带随机数、支撑算法列表等参数。
  2. 服务端收到恳求,挑选适宜的算法,下发公钥证书和随机[ 4 J ;数。
  3. 客户端对服务端证书进行校验,并发送随机数信息,该信息运用公钥加密。
  4. 服务端经过私钥获取随机数信息。
  5. 双方根据以上^ M L 7 #交互的信/ u r E息生成session ticket,用作该衔接后续数据传输的加密密钥。

第3步中,客户端e R G [ M % , ?需求验证服务u ? 0 l 6 7端下发的证书,验证进程有以下两个要点:

  1. 客户I { Q端用本地保存的根i V l 2 l } T W证书解开证书链,确认服务端下发的证书是由可信赖的组织颁布的。
  2. 客户端需3 . ? E j = G ~求查看证书: ^ o的domain域和扩展域,看是v / H ! # – ! 9否包含本次恳求的host。
    假如上述两点都校验经过,就证明当时的服务端是可信赖的,否则就是不可信赖,应当中止当时衔接。

当客户端直接运用IP地址建议恳求c b } l ? F i G时,恳求5 . – G ) W sURL中的host会被替换成HTTP DNS解析出来的IP,所以在证书验证的第2步,会出现domaie b 0 V A hn不匹配的情况,导致SSL/TLS握手不成功。

更多详情请参阅我之前写的一篇关于HTTPS自签名证书上传下载文件的博客:

IOS 网络协议(一) 自签名证书HTTPS文件上传下载(上)

IOS音* y { t y D视频(四十五)P = Z A Q ] a iHTTPS 自签名证书 完结边下边播

  • HTTPS SSL加密树立衔接进程

如下图:

HTTPS SSL加密树立衔接进程

进程详解:

  1. ①客户端的浏览器r – A向服务器发/ 9 : P { ? E送恳求,并传送客户端SSL 协议的版本号,加密算法的种类,发生的随机数,以及其他服务器和客户端之间通讯K E f s g 0 P所需求的各种信息。
  2. ②服务器向客户端传送SSL 协议的版本号,加密算法的种类,随机数以及其他相关信息,同时服! q m Z [务器还将向客户端传送自己的证书。
  3. ③客户端利用服务器传过来的信息验证服务器的合法性,服务器的合法性包括:证书是否过期,发行服务器证书的CA 是否可靠,发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”,服务器证书上的域名是否和服务器的实践域名相匹配。假如合法性验证没有经过,通讯将断开;假如合法性验证经过,将继续进行第四步。
  4. ④用户端随机发生一个用于通讯的“对称暗码”,然后用服务器的公钥(服务器的公钥从过程②中的服务器的^ 1 O ` & v证书中取得)对其加密,然后将加密后的“预主暗码”传给服务器。
  5. ⑤假如服务器要求客户的身份认证(在握手进程中为可选),用户能够树立一个随机数然后Y 7 l对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及加密过的“预主暗码”一起传给服务器。
  6. ⑥假如服务器要求客户的身份认证,服务器有必要检验客户证书和签名随机数的合法性,具体的合法性验证进程包括:客户的证书运用日期是否有用,为客户供给证书的CA 是否可靠,发行CA 的公钥能否正确解开客户证书的发行CA 的数字签名,查看客户的证书是否在证书废止列表(CRL)中。检验假如没有经过,通讯马上中止;假如验证经过,服r F E务器将用自己的私钥解开加密的“预主暗码”,然后履行一系列过程来发生主通讯暗码(客户端也将经过同样的办法发生相同的主通讯暗码)。
  7. ⑦服务器和客户端, Y a f _用相同的主暗码即“` 7 x 1通话暗码”,一个对称密钥用于SSL 协议的安全数据通讯的加解密通讯。同时在SSL 通讯进程s n i } o 3 T t R中还要完结数据通讯的完整性,防止数据通讯中的任何改变。
  8. ⑧客户端向服务器端发出信息,指明后边的数据通讯y E E $ :将运用的过程. ⑦中的主暗码为对称密钥,同时通知服务器客户端的握手进程完毕。
  9. ⑨服务器向客户端发出信息,指明后边的] ! ) q Q数据通讯将运用的过程⑦中的主暗码为对称密钥,同时通知客户端服务器端的握手进程完毕。
  10. ⑩SSL 的握手部分完毕,SSL 安全通道的数C z + 7 w M 2 5据通讯开始,客户和服务器开始运用相同的对称密钥进行数据通讯,同时进行通讯完整性的% 9 H | ~检验。
  • Kingfisher 的认证其实很简略,几行代码就能够了:
//取出downloader单例
lN 4 a $ : U bet downloader = K_ ^ d . Y L ? E !ingfisherMan+ ` 1 J @ Gager.shared.downloader
//信赖ip为106的Server,这儿传入的是一个数组,能够信赖多个IP
downloader.trustedHosts = Set(["192.168.1% S * _ 4 r  ! u.106"])
//运用KingFisher给ImageView赋网络图片
iconView.kf.setImage(with: iconUr% V Gl)

2.2.1.3 Kingfisher装置

装置环境要求:
iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+
S3 r + .wift 4.0+

Pod install

pod   'Kingfisher'

2.2.1.4 Kingfisher核心类介绍

Kingfisher架构图

后续补充…

2.3 资源文件办理库

2.3.1 R.swift

  • 源码下载:R.swi; ? , h Q c 5 X Lft
    R.swift 的星星

2.3.1.1 R.swift 简介

  • R.swifj D T R b Gt 能帮助咱们把项目里面的所有图标资源和本地化语言统一办理,你不在需求每次从资源文件里面去查G L y 7 a } C找图标的称号,也不要去多语言文件里面去查找key了,直接能用函数的办法调用,并且还能有帮助提示。

R.s% X 8 S v @ E Hwift 在Swift项目中取得强类型、主动完结的资源,如图画、字体和segue。

R.swift 使你的代码运用资源具有如下特性:

  1. 全类型,较少类型转换和猜测办法将返回什么
  2. 编译时查看,没有w Z K m y 5更多的不正确的字符串,使您的应用程序溃散在运转时
  3. 主动完结,永远不用再猜P E i j p ) y l 9图画的姓名

例如,运用R.swiz 9 $ $ft 之前你或许会这样写你的代码:

let icon = UIImage(A Y ? E K ( v  3named:7 h ; q , %  "settings-icon")
let font = UIFont(name: "San Frj P `ancisco", si+ Y ze: 42)
let color = UIColor(named: "indicator highlight")
let viewController = CustomViewController(nibName: "CustomView", bundle: nil)
let string = String(format: NSLocalizedString("welcome.withName", comment: ""), locale: NSL9 H * ~ Socale.current, "Arthur Dent")

运用R.swift 之后,你能够这样写代码:

let icon = R.image.settingsIcon()+ A [ 2 [ [
let font = R.font.sanFrancisco(size: 42)
let coc H s 7 t s | R ulor = R.color.indicatorHighlight()
let viewController = CustomViewController(nib: R.nib.customView)
let string = R.string.localizable.welcomeWithName("Arthur Dent")

这儿有官方供给的Demo:Examples , 在realm中运用

看看主动填充的作用多酷:

主动完结图片:

主动完结图片:

编译时查看图片:

在这儿插入图片描绘
2.3.1.1.1 R.swift 特色:

装置R.swift 到您的项目后e V F f W W b,您能够运用R-strd Z M o @ P + zuct拜访8 ( k X 8 p c 2 ]资源。假如结构是过时的,只是树立和R.swift 将纠正任何` [ (失踪/改变/增加的资源。

R.swift 现在支撑这些类型的资源:

  • Images

  • Fonts

  • Resource files

  • Co s Golors

  • Loc? 5 / U – Falized strings

  • Storyboards

  • Segues

  • Nibs

  • Reusable cells

2.3.1.2 R.swift 运用

  • Images
    R.swif= r _ 7 it 将在你0 O J Y 3 (的包中找到财物目录和图画文件的图画。

没有运用R.swift 这样拜访图片

let settingsIcon = UIImage(named: "settings-icon")
let gradientBacn ^ B % Wkground = UIImage(named: "gradient.jpg")

运用R.swift 后这样拜访:

let settingsIcD - = [ 1 k , aon = R.image.settingsIcon()
let gradientBackground = R.image.gradientJpg()

此外R.swift 还支撑文件X J + %夹中分组的办法:

挑选“供给称号空间”分组财物成果:

选这图片

然后这样运用:

let image = R.image.menu.icons.fi* ! m y ? @ @ brst()
  • Fonts

没有运用R.swift 这样拜访:

let lightFontTitle = UIFont(M ) e a o ] # Y 0name: "Acme-Light", size: 22)

运用R.swift 后这样8 U O z H ] X拜访:

let lightFontTitle = R.font.acG % l I &meLight(size: 22)

提示:体系字体也需y 2 a T求这个吗?
看一下UIFontComC ) Z W [ h 3 S Bplete库,它有一; n j E个类似的处理方案,用于苹果公司发布的iOS字体。

  • Resource files

没有运用R.swift 这样拜访:

let jsonURL = Bund{ X u G Z # ^ ^ kle.maY l {  N M /in.url(fo5 * , / : O b erResource: "seed-data", withExtension: "json")
let jsonPath = Bundle.main.path(forResource: "seed-data", ofTypey Z 9: "json")

运用R.swS C z _ A / kift 后这样拜访:

let jsonURL = R.file.seedDataJson()
let jsonPath = R.file.seedDataJson.path()
  • Colors

没有运用R.swift 这样拜访:

view.backgroundColo1 C b 0 W qr = UIColor(named: "primary background")

运用R.swO H ) 9 m 3 jift 后这样拜访:

view.backgroundColor = R.coloU = t g = _r.primaryBackground()
  • Localized striny @ 4gs

没有运用R.swift 这样拜访:

let welcomeMessage = NSLocalizedString("welcome.message", comment: / P ~ & z g {"")
let se$ r 9 ) uttingsTitle = NSLocalizedString("title", tableName: "Settings", comment: "")
// Formatted strings
let welcomeName = String(format: NSLocalizedStrX  C `ing("welcome.withName", comment: ""), lD 3 f o - C !ocale: N$ U ? S J b u .SLocale.curre) a k tnt,] g G "Alice")
// Stringsdict files
let progress = Strij F ` 7ng(format: NSLocalizedStrO ^ n s 5 u n eing("copy.progress", comment- S s ` { 9 C T /: "")2 6 q I : 4 E _, locale: NSLocale.current, 4, 23)

运用R.swi; = 2 dft 后这样拜访:

// Localized strings are( i P d = 9 grouped per table (.strings file)
let welcomeMesh L V 5 | = } ksage = R.string.localiz( z U N E m :able.welcomeMessage()
let settingsTitle = R.string.se( 7 ^ O y s Cttings.title()
// Functions with parameters are generated for format strings
let welcomeName = R.string.localizable.welcomeWi5 I E f zthName("Alice")
// Functions with named argument labels are generatedn i } V C ? for stringsdict keys
let progressu .  3 * = R.string.localizable.copyProgress(completed: 4,8 b d O  C K 6 & total: 23)
  • Storyboards

没有运用R.swif] i W tt 这样拜访:

let s$ e K ` w K & O 5toryboard = UIStoryboard(name: "Main", bq | h S bundle: nil) V ()
let initialTabBarController = storyb# T q R p { /oard8 l d.instantiateInitialViewController() as? UITabBarController
let settingsA ^ A * A XController = storyboaU  3 8rd.instantiateViewConm 9 } G : = m w 6troller(withIdentifier: "settingsController")N U { e  as? SettingsController

运用R.! h 4 W ^ |swift 后这样拜访:Q 7 * 7 M | # 3

let storyboard = R.storyboard.main()
let initialTabBarController = R.storyboard.main.initialViewController()
let settingsController = R.storyboard.main.settingsController()
  • Segues

没有运} H A H用R.swift 这样拜访:

// Trigger segue with:
performSegue(withIdentifier: "openSettings", sender: self)
// An2 N + - G & G p $d then prepare it:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let settingsControlW J yler = segue.destination as? SettingsController,
let segue = segueQ { P as?9 + W O N CustomSettingsSegue, segue.identifier == "openSettings" {
segue.animationType = .LockAnimation
settingsController.lockSettings = true
}
}

运用R.swift 后这样拜访:

// Trigger segue with:
performSegue(witF E Y # ? Q Q v _hIdentifier: R.segue.overviewController| 2 z ].openSettings,W O =  + o sender: self)
// And then prepare it:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let typedInfo = R.segue.overviewControlle} b  * } 6r.openSettings(segue: segue) {
typedInfo.segue.animationZ Q 6 ] [ * o #Type = .LockAnimation
typedInfo.d6 o C { f & 7 +estinationViewController.lockSettings = true
}
}

提示:看看SegueM5 g J u D 2 ] panager库,它使segues块为根底,并与r.s ft兼容。

  • Nibs

没有运用R.swift 这样拜访:

let nameOfNib = "CustomView"
let customViewNib = UINib(nibName: "Cuo y * # t tstomView", bundle: nil)
let rootu f L @ j : dViews = customViewNib.instantiate(withOwner: n2 w 8 ] = n W il, options: nil)
let customView = rootVm q -iews[0] as? Cus^ ) 1tomView
let viewControllerWithNib = CustomViewController(nibNa8 I v qme: "Cu] N G #stomView", bundle: nil)

运用R.swift 后这样拜访:

leF K  v x { } [t nameOfNib = R.nib.cuU S mstomView.name
let customViewNib = R.nib.customView()
let rootViews = R.nib.customView.instantiat( h ^ y l q be(withOwner: nil)
let customView = R.nip u # |b.customView.firstView(owner: nil)
let viewControllerWO K m p + w c ` 3ithNib = CustomViewController(nib: R.nib.customView)
  • Reusable cells

(1) 重用TableViewCell

没有运用R.swift 这样拜访:

class FaqAnswerConQ . 5 ltrollerW ; m F l Q T 8 J: UITableVJ , # R PiewController {
override func viewDidLoad() {
super.viewDidLoad()
let textCellNib = UINib(nibName: "TextCell", bundle: nil)
tableView.register(textCellNib, forCellReuseIdentifier: "TextCellIdentifier")
}
override func tableView(_ tableView: UITableView, cellFo- k & * 8 rRowAt ind& - - B e I a 3 XexPath: IndexPath) -> UITableViewCell {
let textCell = tableView.dequeueReusableCell(withIdentifier: "TextCellIdentifier", for: indexPath) as! TextCell
textCell.mainLabel.text = "Hello World"
return textCell
}
}

运用R.swia N A O R K C K 6ft 后这样拜访:

在可重用单元格界面生成器“特色”查看U = _ { Y ] @ G 2面板上,将单元格“标识符”字段设置为要注册和退出行列的相同值。

class FaqAnswerController: UITableViewCoZ W Jntroller {
overriJ * x 2 i j e x Ode func viewDidLoad() {
super.viewDidLoad()
tableView.register(RP T 9.nib.textCell)
}
override func ta9 @ EbleVie} l r F ~ d 4 ` 7w(_ tableView: UITabb + Vle6 4 O V t A , VView, cellForRowAt indexPath: IndexPath 8 9h) -> UITableViewCell {
let teb * rxtCell = tabI ] K @ s B J qleView.dequeueReusableCell(withIdentifier: R.U q M R A ureuseIdentifp R Yier.te/ x l  ; 1 P g ^xtCell, for: indexPath)!
textCell.mainLabel.text = "Hello World"
return textCell
}
}

(2) 重 _ 5 w ) V用Collectj 7 . w GionViewCell

没有运用R.swift 这样拜访:

class RecentsController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
let talkCellNib = UINib(nibName: "TalkCell", bundle: nil)
collectionView?.register(talkCellNib, forCellWithReuseIdentifier: "TalkCellIdentifier")
}
override func collc ) p ! 7 /  gectionView(_ collectZ ! f |ionView: UICollectiU I  Q Z monView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReE O 6 q v  D 1 )usableCell(withReuseIdentifier: "Tat 4 / O f * ! 9lkCellIdentifier", for: indexPath) as! TalkCell
cell.configureCell("Item (indexPath.item)")
return cell
}
}

运用R.swift 后这样拜访:

在可重用单元格界面生成器“特色”查看面板上,将单元格“标识符”字段设置为要注册和退出行列的相同值。

cS m h 1 Klass RecentsController n n ( | # U 4: UICollectionViewController {
override func viewDi1 5 mdLoad() {
super.viewDidLoad()
collectionView?.register(R.nib.talkCy N n g / P Nell)
}
override func collectionm M nView(_ collectionView: UICollectionView, cellForItemAt index W [ [ ? ZPath: IndexPath) -> UICollectionViewCell {
let ce! 5 Vll = collectionView.dequeueReusableCell(withReuseIdentifier: RW ? R ^ / V.reuseIdentifier.talkCell, for: indexPath)!
cell.configureCell("Item (indexPath.item)")
return c5 w - ^ 8 B Well
}
}

2.3.1.2 R.swify E o m v o C g Rt 装置

CocoaPods是推荐W @ y – ! –的装置办法,因为这样能够防止在项目中包含任何二进制文件。

注意:R.swift 是一个用于构建过程的东西,它不是一个动态库。因而? m 3 .,它不或许装置Carthage。

CocoaPods(推荐) 装置过程:

  1. 增加pod 'R.swift'到您的Podfile和运转pod install
  2. 在Xcode中:单击文件列表中的项目,在“方针”下挑选方针,单击“构建阶段”选项卡,经过单击左上角的小加号图标增加新的运转脚本阶段
  3. 将新的运转脚本阶段拖动s ] x %到编译源阶段之上,并在查看pod清单s 2 G I G ; t ) E之下。确定,打开并粘贴以下脚本:"$PODS_ROOT/R? N . K ^ h X N.swift/rswift" geneH m T m Mrate "$SRCROOT/O F * [ N * RR.generated.swift"
  4. $TEMP_DIR/rswift-lastrun增加到“输入文件”中,将$SRCROOT/R.genero e uated.swift增加到构建阶段的“输出文件”中
  5. 树立你的项目,在Finder中你会看到一个R.! , @ [ C Q @generated.swift$SRCROOT文件夹中,拖动R.generated.swift文件到你的项目中,假如需求,撤销勾选CV B ~ s v ~opy项

2.3.2 SwiftLint

  • 源码下载: SwiftLint

2.4 秘钥办理库

2.4.1 KeychainAccess

  • 源码下载: KeychainAo y 0ccess

2.5 主n c R动布局库

2.5.1 SnapKit

  • 源码下载: SnapKit

2.6 UI相关库

2.6.1 NVActivityIndicatorView

  • 源码下载:NVActivityIndicatorView

2N E / a x.6.2 ImageSlidershow/KingfisS y L Cher

  • 源码下载: ImageSlidershow/Kingfisher

2.6.3 DZNEmptyDataSg n 5 B 6 %et

  • 源码下载: DZNEmptyDataSet

2.6.4 Hero

  • 源码下载:Hero

2.6.5 Localize-Swift

  • 源码下载: Localize-Swift

2.6.6 RAMAnimatedTabBar` p % FController

  • 源码下载:RAMAnimatedTabBarController

2.6.7 AcknowLiI C 7 j 9 G Z wst

  • 源码下载: AcknowList

2.6.8 KafkaRefresh

  • 源码下载:KafkaRefresh

2.6.9 WhatsNewKit

  • 源码下载: WhatsNewKit

2] = ! , 3 k _ { !.6.10 Highlightr

  • 源码下载: Highlightr

2.6.11 DropDown

  • 源码下载:DropDown

2.6.12 Toast-Swift

  • 源码下载:Toast-Swi( C = a W #ft

2.6.13? Z z $ 3 HMSegmentedControl

  • 源码下载: HMSegmentedContr, * L q 5ol

2.6.14 Fl, e g M ! b m ^ –oatingPanel

  • 源码下载: FloatingPanel

2.6.15 MessageKit

  • 源码下载: MessageKit

2.6.16 MultiProgressView

  • 源码下载: MultiProgressView

2.6.17 IQKeyboardManagu 6 %erSwift

  • 源码下载:IQKeyboardManagerSwift

2.7 日志办理库

2.7.1L # $ $ CocoaLumberjack/Sd s U k q e [ d wwift

  • 源码下载 d d:CocoaLumberjack/Swift

2.8 数据埋点库

2.8.1 Umbrella

  • 源码下载: Umbrella

2.8.2 Umbrella/Mixpanel

2.8.3 Umby : :rella/Firebase

2.8.b ) ? w C j g w u4 Mixpanel

  • 源码下载z } { x w w H:Mixpanel

21 A o D.8.5 Firebace/Analytics

2.E D N ( M D9 广告东西点库

2.9.1 Fireq I o c p r K c Sbase/AdMob

2.9.2 Google-Mobile-Ads-SDK

2.10 性能优化相关库

2.10.1 Fabric

2.10.2 Crash@ U y t @ nlytics

2.11 其他东西类库

2.11.1 FLEX

  • 源码下载: FLEX

2.11.2 SwifterSwift

  • 源码下载: SwifterSwift

2.11.3 BonMot

  • 源码下载:BonMot

2.11.4 DateToolsSwift

  • 源码下载:DateToolsSwift

2.11.5 SwiftDate

  • 源码下载: SwiftDate

3. SwiftHub项目选用的架构剖析