Castie!

正态分布, 优劣伴生

北冥有鱼,其名为鲲(kūn)。鲲之大,不知其几千里也;化而为鸟,其名为鹏。鹏之背,不知其几千里也;怒而飞,其翼若垂天之云。是鸟也,海运则将徙于南冥。南冥者,天池也。


北海若曰:“井鼃不可以语于海者,拘于虚也;夏虫不可以语于冰者,笃于时也;曲士不可以语于道者,束于教也。今尔出于崖涘,观于大海,乃知尔丑,尔将可与语大理矣。

RxSwift 函数式过滤运算符实操

接着上一部分, 我们已经完成了函数式编程的简单实践, 也同时接触了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:

点击下方链接跳转!!

🌟 源码 请点这里🌟 »> 喜欢的朋友请点喜欢 »> 下载源码的同学请送下小星星 »> 有闲钱的壕们可以进行打赏 »> 小弟会尽快推出更好的文章和大家分享 »> 你的激励就是我的动力!!

最近的文章

RxSwift 函数式映射运算符实操

一路走来, 感觉RxSwift也不像之前一样晦涩难懂了, 甚至渐渐的喜欢上了这种响应式编程的思想, 将对象作为可观察对象并进行订阅, 加上过滤操作符的协作, 一切的逻辑运算都在后台线程执行, 本节所要讲的是Rx中最为强大的功能 — 映射, 代码见:github本节所需的一些关于映射的基本知识已经更新到github代码中的playground文件中, 没有接触过响应式编程的同学请和之前一样先行在playground中了解概要以便更好的理解本文. 本节我们就通过案例逐步精讲映射操作符在网络...…

移动开发继续阅读
更早的文章

RxSwift 大神们都在看的响应式

上个系列的架构设计, 从MVC -> MVVM -> MVP -> Router -> Downgradable, 我们实现了通过分层将各个组件进行解耦并动态的升降级页面, 这一系列准备说说如何通过RxSwift进行响应式编程更好的和架构设计进行协同. 代码见:github这系列所述案例是通过Ray家的RxSwift一书进行笔记, 在学习本篇之前, 请确保熟练掌握Swift3基础语法并具备中级iOS开发能力. 对于RxSwift的概念不是很清晰的同学可以参照本文...…

移动开发继续阅读