接着上一部分, 我们已经完成了函数式编程的简单实践, 也同时接触了
Photos
框架带来的自定义的便利, 主要熟悉了Variable
,Subject
作为Observable
进行订阅subscribe
并通过处置包DisposeBag
处理的具体案例, 这一节我们来进一步完善相片拼图. 代码见:github
本节所需的一些关于订阅信息过滤的基本知识已经更新到github代码中的playground文件中, 没有接触过响应式编程的同学请和之前一样先行在playground中了解概要以便更好的理解本文. 在进行对项目的完善之前我们先来看一个问题:
example(of: "Sharing subscription") {
var start = 0
func getStartNumber() -> Int {
start += 1
return start
}
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()
}
numbers.subscribe(
onNext: { el in
print("element [\(el)]")
},
onCompleted: {
print("---------------")
}
)
numbers.subscribe(
onNext: { el in
print("element [\(el)]")
},
onCompleted: {
print("---------------")
}
)
}
---Example of: Sharing subscription ---
element [1]
element [2]
element [3]
---------------
element [2]
element [3]
element [4]
---------------
同样是对于可观察对象进行订阅为什么会出现两种不同的结果? 问题是,每次调用subscribe(…)时,都会为该订阅创建一个新的Observable,并且每个副本不能保证与之前的相同。 即使Observable确实产生了相同的元素序列,为每个订阅生成相同的重复元素也是没有意义的。
example(of: "Sharing subscription Fix") {
var start = 0
func getStartNumber() -> Int {
start += 1
return start
}
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()
}
let shareNumbers = numbers.share()
shareNumbers.subscribe(
onNext: { el in
print("element [\(el)]")
},
onCompleted: {
print("---------------")
}
)
shareNumbers.subscribe(
onNext: { el in
print("element [\(el)]")
},
onCompleted: {
print("---------------")
}
)
}
---Example of: Sharing subscription Fix ---
element [1]
element [2]
element [3]
---------------
---------------
如果需要进行共享订阅,可以使用share()运算符。 Rx代码中的常见模式是通过过滤每个结果中的不同元素来从同一个源中创建几个序列。
回到我们的项目中来看如何将share()运算符运用的实际, 将ViewController中的代码修改如下:
class ViewController: UIViewController {
...
fileprivate var imageCache = [Int]() //update
...
}
//update
fileprivate func updateNavigationIcon() {
let icon = imagePreview.image?.scaled(CGSize(width: 22, height: 22)).withRenderingMode(.alwaysOriginal)
navigationItem.leftBarButtonItem = UIBarButtonItem(image: icon, style: .done, target: nil, action: nil)
}
@IBAction func actionAdd() {
// images.value.append(UIImage(named: "Castie!")!)
let albumViewController = storyboard?.instantiateViewController(withIdentifier: "AlbumViewController") as! AlbumViewController
//update
let newImages = albumViewController.selectedImages.share()
newImages.takeWhile { [weak self] image in
return (self?.images.value.count ?? 0) < 6
}.filter { newImage in
return newImage.size.width > newImage.size.height
}.filter { [weak self] newImage in
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(albumViewController.bag)
newImages.ignoreElements().subscribe(
onCompleted: { [weak self] in
self?.updateNavigationIcon()
}).addDisposableTo(albumViewController.bag)
navigationController?.pushViewController(albumViewController, animated: true)
}
@IBAction func actionClear() {
images.value = []
//update
imageCache = []
}
我们来具体分析actionAdd()
这段代码, 首先, 我们通过share()
操作符对可观察对象进行订阅共享takeWhile()
操作符将得到的订阅信息进行过滤, 返回最大六张图片和之前对点击按钮的屏蔽逻辑进行了对应, filter()
操作符就是传统的函数式过滤, 返回图片宽度大于高度的图片及通过图片字节数和缓存池中的字节数判断是否为同一张图片. 这里我们新加了一个订阅 ignoreElements()
操作符是忽略所有的onNext()
事件, 只接收onCompleted ()
事件. UI层面我们在左上角添加了一个对应图片的缩略图.
//update
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
selectedImagesSubject.onCompleted()
}
接下来我们就需要对AlbumViewController生命周期结束的时候对可观察对象进行onCompleted()
调用, 执行之前的订阅代码修改UI.
运行成功后, 我们顺利的看到左上角的缩略图显示了, 并且不能重复添加大于六张图片及只允许宽度大于长度的图片. 但我们这里有一个小问题, 当我们第一次访问相册的时候, 会弹出一个提示框对用户行为进行授权, 但授权完成后, 图片并没有显示出来, 这是因为懒加载的时候在授权验证前并没有拿到任何的数据. 我们来看看如何解决. 我们新建一个文件, 并对PHPhotoLibrary进行类扩展.
PHPhotoLibrary可以看成是一个用户图库,包含了一些的图片和相册,同时包含本地的和iCloud中的资源。当Photos app发生图片的修改、增加、删除等改变时,使用PHPhotoLibrary来做一些刷新UI,保存数据等响应动作,同时也可以注册观察者(使用registerChangeObserver方法),当Photos app内容发生改变的时,会触发代理方法photoLibraryDidChange。
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 { newStatus in
observer.onNext(newStatus == .authorized)
observer.onCompleted()
}
}
}
return Disposables.create()
}
}
}
这里对于刚学习Swift的同学可能并不是很好理解, 这里我们对PHPhotoLibrary
扩展了一个authorized
属性, 并通过计算属性返回了一个可观察对象, 对观察对象进行授权校验, 如果校验过就执行操作, 没有校验过就请求授权校验, 这里开了一个GCD的主线程, 但一般来说在订阅中是观察器不应该阻止当前线程. 接下来我们在AlbumViewController中进行使用.
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
//update
let authorized = PHPhotoLibrary.authorized.share()
authorized.skipWhile { $0 == false }.take(1).subscribe(onNext: { [weak self] _ in
self?.images = AlbumViewController.loadImages()
DispatchQueue.main.async {
self?.collectionView?.reloadData()
}
}).addDisposableTo(bag)
authorized.skip(1).takeLast(1).filter { $0 == false }.subscribe(onNext: { [weak self] _ in
guard let errorMessage = self?.errorMessage else { return }
DispatchQueue.main.async(execute: errorMessage)
}).addDisposableTo(bag)
}
我们通过订阅共享的形式进行, 第一个订阅授权验证当验证成功后进行获取系统相册图片, 并在主线程刷新UI, 第二个是发起请求验证后仍没有验证后, 主线程执行错误提示弹窗. 代码中的skipWhile()
, take()
,skip()
, takeLast()
操作符, 不理解的同学可以到项目中的playground中先行了解.
fileprivate func errorMessage() {
alert(title: "No access to Camera Roll",
text: "You can grant access to Combinestagram from the Settings app")
.take(5.0, scheduler: MainScheduler.instance)
.subscribe(onDisposed: { [weak self] in
self?.dismiss(animated: true, completion: nil)
_ = self?.navigationController?.popViewController(animated: true)
}).addDisposableTo(bag)
}
errorMessage()
中需要讲一下的是.take(5.0, scheduler: MainScheduler.instance)
从给定时间段的源序列中获取元素。 一旦时间间隔过去,完成结果序列。我们在ViewController中也更改如下:
fileprivate func setupObservable() {
//update
images.asObservable().throttle(0.5, scheduler: MainScheduler.instance).subscribe(onNext: { [weak self] images in
guard let preview = self?.imagePreview else { return }
preview.image = UIImage.collage(images: images, size: preview.frame.size)
}).addDisposableTo(bag)
images.asObservable().subscribe(onNext: { [weak self] images in
self?.updateUI(images: images)
}).addDisposableTo(bag)
}
.throttle(0.5, scheduler: MainScheduler.instance)
和刚才的take(_:scheduler:)
非常相似, 是在一定时间间隔内过滤掉之前的元素只留下最新的元素,
演示效果:
About:
🌟 源码 请点这里🌟 »> 喜欢的朋友请点喜欢 »> 下载源码的同学请送下小星星 »> 有闲钱的壕们可以进行打赏 »> 小弟会尽快推出更好的文章和大家分享 »> 你的激励就是我的动力!!