1 優(yōu)化現(xiàn)有應(yīng)用
前面一章引入了 RxSwift 中的函數(shù)式編程的方面, 即 Operator.
下面就來使用之前學(xué)過的一些過濾型操作符, 看看在實際工程中如何使用它們.
當然下面的這些代碼只是引路作用, 并非是進入所謂的 "操作符最佳實踐". 先入門, 然后再慢慢提高優(yōu)化!
2 共享 Observable 序列, 實現(xiàn)一些過濾操作
先來看一個情況:
let numbers = Observable<Int>.create { observer in
let start = getStartNumber()
observer.onNext(start)
observer.onNext(start+1)
observer.onNext(start+2)
observer.onCompleted()
return Disposables.create()
}
而 getStartNumber()
方法是這樣的:
var start = 0
func getStartNumber() -> Int {
start += 1
return start
}
下面對該序列進行觀察.
numbers
.subscribe(onNext: { el in
print("element [\(el)]")
}, onCompleted: {
print("-------------")
})
如果多次觀察這段代碼, 最后的輸出會在每次觀察的時候都不一樣.
這是為什么呢?
因為每次觀察者開始觀察的時候, 都會去執(zhí)行 Create 塊中的代碼, 從而導(dǎo)致序列的變化.
有時這樣的變化是有意為之, 但有時卻不希望發(fā)生.
如果不希望每次觀察都是去重新執(zhí)行 create 塊, 則可以使用 share()
操作符.
let newPhotos = photosViewController.selectedPhotos
.share()
這樣的話, 任何在 newPhotos 上的新觀察者得到的都是同樣的可觀察序列.
實際上的原理是這樣的: 當序列的觀察者數(shù)量從 0 變化到 1 的時候, share 會去創(chuàng)建一個 Observable 序列出來. 當觀察者再增加時, share 則會直接使用已創(chuàng)建的序列交給觀察者. 如果在該序列上的所有觀察者都被釋放了. 則 share 會將創(chuàng)建出來的序列銷毀. 而如果后面又有新的觀察者, 則又會重復(fù)上述過程.
另外 share 操作符有一個特性, 即它不會提供在觀察者開始觀察之前序列已發(fā)射的內(nèi)容. 如果需要 share 的共享特性, 又需要知道最后一個發(fā)射的內(nèi)容, 則可以使用 shareReply(_)
操作符, 它可以指定一個緩存, 緩存之前發(fā)射過的若干事件.
2.1 ignoreElments 操作符
這個操作符的作用是只允許 complete 或 error 通過. 這樣的話, 就可以用來觀察完成或是錯誤. 當然這里只是一個假設(shè)的使用情況, 不對 next 事件作出響應(yīng)也是一個結(jié)果.
2.2 實現(xiàn)過濾掉相同圖片的功能
另外如果想添加照片的時候只添加不同的文件名的圖片, 則也可以使用過濾.
但是多個 UIImage 對象間的區(qū)別不能通過地址來識別, 也無法通過名字或 URL 識別.(除非是自定義的子類. 實際使用的時候就可以繼承并在子類中添加類似屬性用于識別不同的 Image.)
不過在本例中僅使用字節(jié)長度作為判斷依據(jù), 防止跑題.
newPhoto
.filter({ newImage in
// 過濾豎向的圖片.
newImage.size.width > newImage.size.height
})
.filter({ [weak self] newImage in
// 過濾相同大小的圖片, 防止重復(fù)選擇
let len = UIImagePNGRepresentation(newImage)?.count ?? 0
guard self?.imageCache.contains(len) == false else { return false }
self?.imageCache.append(len)
return true
})
.subscribe(onNext: { [weak self] newImage in
guard let images = self?.images else { return }
images.value.append(newImage)
}, onDisposed: {
print("completed photo selection")
})
.addDisposableTo(photosViewController.bag)
2.3 實現(xiàn)當只有滿足條件時才會允許 next 消息通過
這里需要使用 takeWhile
操作符. 可以為 takeWhile
提供一個布爾判斷, 如果值變?yōu)榧僦? 就可以取消掉之后的所有元素.
newPhoto
.takeWhile({ [weak self] _ in
return (self?.images.value.count ?? 0) < 6
})
// ... 下面還是之前的過濾代碼等內(nèi)容
這樣一來, 條件為 false 的時候, 就不會允許 next 通過了.
3 優(yōu)化照片選擇器
下面首先來構(gòu)造一個自定義 observable, 然后通過不同的過濾器來操作它, 提升用戶體驗:
這個 observable 是針對某個權(quán)限的請求和允許情況的:
import Foundation
import Photos
import RxSwift
extension PHPhotoLibrary {
static var authorized: Observable<Bool> {
return Observable.create({ observer in
DispatchQueue.main.async {
if authorizationStatus() == .authorized {
observer.onNext(true)
observer.onCompleted()
} else {
observer.onNext(false)
requestAuthorization({ status in
observer.onNext(status == .authorized)
observer.onCompleted()
})
}
}
return Disposables.create()
})
}
}
上面的代碼中, 當外界有新的觀察者開始觀察的時候, 都會觸發(fā) create 方法的執(zhí)行. 所以每次都會去判斷權(quán)限.
使用 DispatchQueue.main.async 的意思是: 首先 DispatchQueue.main 表示在主線程中執(zhí)行, 而 DispatchQueue.global() 是在后臺線程中執(zhí)行, 且 sync 表示當前線程會等待該工作塊的執(zhí)行完畢后再繼續(xù)執(zhí)行, 而 async 的話, 當前線程不會等待該工作塊的執(zhí)行完畢.
sync 方式執(zhí)行的工作塊, 當前線程會等待該工作塊執(zhí)行完畢后再繼續(xù)執(zhí)行. 而 async 方式執(zhí)行的工作塊當前線程是不會等待它結(jié)束的.
3.1 開始外界的觀察操作
下面就在外界開始觀察, 若權(quán)限是允許的情況下, 則重新加載照片.
由于權(quán)限的請求只能一次, 故當前狀態(tài)有兩種:
-
用戶第一次運行程序, 第一次請求權(quán)限, 且點擊的是 ok.
false---true---completed
-
之后的運行過程, 如果之前允許過該權(quán)限.
true---completed
經(jīng)過分析, 我們知道要重新加載照片的前提就是遇到 true, 而 true 的事件肯定是最后一個 next 事件.
下面就開始觀察:
let authorized = PHPhotoLibrary.authorized.share()
authorized.skipWhile({ elem in
elem == false
}).take(1).subscribe(onNext: { [weak self] _ in
self?.photos = PhotosViewController.loadPhotos()
DispatchQueue.main.async {
self?.collectionView?.reloadData()
}
}).addDisposableTo(bag)
不過在 RxSwift 中盡可能不要使用 GCD 來切換線程, 更多地是使用 scheduler 來達到目的. 詳見 15章.
3.2 當用戶不允許時顯示錯誤信息
每次這樣的情況都需要先分析好當前的 Observable 中的事件序列是個什么樣的情況.
4 利用時間的操作符
略看.
第六章結(jié)束.