RxSwift 4 实际动手

现在开始 observable 和 Subject 的实操了.

从下面开始, 就来看看如何在实际的开发过程中去运用这些新工具了.

4.1 开始

这里讲述使用 RxSwift 以及一些新的内容来创建一个 app, 且以 reactive 的方式来构造.

首先新建一个工程,并配置好了....

在 ViewController 中添加:

private let disBag = DisposeBag()
private let images = Variable<[UIImage]>([])

因为 disposeBag 是管理 VC 里面的 Observable 的, 需要的结果是当 VC 被释放的时候, 引起 disposeBag 的释放, 从而释放所有被管理的 Observable, 所以这里需要一个 private 的 disposeBag.

images 是一个 �Variable, 通过它即可以获取到当前所有的图片, 也可以观察它, 在新图片设置的时候做出响应.

不过由于这个 VC 是 window 的根 VC, 实际上它不会被释放, 所以�所有被管理的 Observable 在程序结束也不会得到释放.

在本章后面可以看到一些关于内存管理的技巧.�

利用 Variable 作为属性来存放属性值, 并且在属性改变的时候可以通知观察者, 正所谓一举两得.

images.asObservable()
    .subscribe(onNext: {[weak self] photos in
        guard let imView = self?.imageView else { return }
        if photos.count > 0 {
            imView.image = photos[0]
        } else {
            imView.image = nil
        }
        print(photos.count)
    })
    .addDisposableTo(disBag)

在刚开始学习的时候, 暂时把添加观察者的代码放在 viewDidLoad 里面, 在后面还要学习如何将它们放在不同的类中, 最后是看如何在 MVVM 中去设置观察者.

下面再来看一个更复杂一点功能.

驱动更加复杂的 UI

比如有如下的需求:

  • 当没有图片的时候, 自动把 remove 按钮置为不可用.
  • 当超过一定数量的时候就不能再添加.
  • 让 VC 的标题也可以跟着改变.(导航栏标题)

可以�看到这些需求�都是对 UI 的操作, 可以把这些操作都放到一个方法中, 然后在观察者中调用即可.

4.3 使用 Subject 实现 VC 间交流

下面�来实现 PhotosViewController 和 main view controller 之间的交互.

假设使用一般的 Cocoa 通信手段, 可能就是利用代理机制, 设置被 push 出来的 VC 的代理为之前栈顶的 VC. 然后通过代理方法来传递数据.

如果不使用代理, 传统的方式还有完成块, 通知等手段.

但是使用 RxSwift 的话, 就可以把这样的通信用一种统一的方式实现: Observable. 而且无需任何的 protocol 等事先的约定, 因为 Observable 本来就可以携带任意内容.

具体实现的时候, 是利用一个 Subject 属性, 然后暴露它的 Observable(序列)即可供外界观察.

比如被 push 的控制器有一个属性:

    fileprivate let selSubject = PublishSubject()
    var selPhotos: Observable {
        return selSubject.asObservable()
    }

这样的话, 即防止了外界�向 subject 发送 next 消息, 又可以保证外界观察顺利进行.

这样的�套路就是�两个 VC 使用 Rx 方式通信的基本套路.

而两个 VC 间的关系也清晰明了, 即原栈顶控制器 A 将 B �push 到栈顶, 在 A 中 可以访问 B 的�属性, 这样就可以设置对于 B 中属性的观察. 而 B 实际上对 A 一无所知.

而在之前的控制器 A 中就可以对 B 暴露出来的观察接口进行观察了:

photoVC.selPhotos.subscribe(onNext: {
    print($0)
}, onError: { _ in
    print(MyError.error1)
}, onCompleted: {
    print("完成")
}, onDisposed: {
    print("释放")
})
.addDisposableTo(disBag)

4.4 使用哪个 VC 的 disposebag?

由于在上面的代码中并没有�合适的时机控制 subject 发送 complete 事件. 故该观察者也不会被释放.

这样的结果是内存永远都被占用着!!

为何如此? 以及如何避免? 可以利用 RxSwift 提供的一个 debug 手段, 名为 Resources, 它可以给出当前所有分配了空间的 observable, observer 以及 disposable 的数量.

由于性能相关的原因, 这个功能默认是关闭的. 不过我们可以在 debug 的时候打开这个功能.

打开 Resource 计数功能

只需要加一个编译标志即可打开, 但添加的手法比较特殊:

  • 使用 pod 的话:

    在 podfile 中添加如下内容:

    # enable tracing resources
    post_install do |installer|
        installer.pods_project.targets.each do |target|
            if target.name == 'RxSwift'
                target.build_configurations.each do |config|
                if config.name == 'Debug'
                    config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['-D','TRACE_RESOURCES']
                end
            end
        end
    end
    end
    

    这样会在 RxSwift 工程中的所有 debug schema 中查找 rxswift 并设置编译标志.

    添加后, 只需要执行 pod install 即可.

  • 使用 Carthage 的情况:
    首先需要建立一个名为resources.xcconfig的文件, 内容为:

    OTHER_SWIFT_FLAGS = -DTRACE_RESOURCES
    

    然后使用下面的命令来重新�编译 rxswift:

    XCODE_XCCONFIG_FILE="/resources.xcconfig" carthage update --configuration Debug --platform iOS --no-use-binaries
    

    不过执行这条命令可能会要点时间.

有了这个资源计数工具, 就可以开始追踪内存泄漏问题了.

追踪 Leaks

打开是资源计数后, 要使用就比较简单了:

比如要检查资源, 可以在该 VC 的viewwillappear里面打印:

print("resources: \(RxSwift.Resources.total)")

上述就是打印当前的总资源数量.(有 observable, observer, disposable)

当在某些时候发现资源数量只涨不跌的时候, 就可以知道是哪里出问题了.

还有, 观察者资源被释放的时候是:

  • 遇到 complete 被自动释放
  • 遇到 error 时被自动释放
  • disposebag 释放时随之一起释放.
  • 或是手动调用 dispose 释放.

这里说的资源释放都是观察者资源释放, 因为 observable 的生命周期是和它的宿主相关的. 比如 VC 中的属性, 如果 VC 释放了, 里面的 subject 或 observable 属性也会一起就被释放了.

而一般被观察者被释放后, 就需要观察者也释放, 因为被观察者都没有了还观察什么.

方案一: 不将观察者放入自身所处的对象的 disposebag, 而是放入被观察者所处的对象的 disposebag.

这样是一个方案, 但是更好的方案是寻找到一个时机, 比如当 VC 从栈顶被弹出的�时候, 只要它被释放, 就可以发送 complete 事件, 从而引起资源的释放.

方案二: 在被观察者所处的上下文中寻找更加合适的时机, 通过发送 complete 来释放资源.

两个方案第二个明显更加优秀. 但有些时候无法判断 complete 时机时, 也只能是使用方案一了.

下面再来看一个比较有意思的内容: 如何将某个方法或函数转换为一个 Reactive 的超酷类.

创建自定义 observable

之前的内容都是在用 Variable, PublishSubject 或是 Observable.(还有一个 replay 的没有怎么介绍, 需要去发现其用途.)

下面就来介绍如何将普通函数封装到一个响应式的类中.

�实现时, 将某函数封装到类中, 然后只是通过一个 Observable 来暴露给外界.

封装一个自由函数

现在有一个自由函数 savePhoto, 且有一个类 PhotoWriter, 现在想要将该函数封装到类中.

typealias Callback = ()->Void
private var callback: Callback?
private init(callback: @escaping Callback) {
    self.callback = callback
}

下面再来为 savePhoto 方法设置一个�完成回调方法:

    func image(_ image: UIImage, didFinishSavingWithError error: MyError?) {
        callback?()
    }

它作为 savePhoto 的回调方法, 可能会带回 error.

下面只剩下创建一个 Observable 来使用 callback 了.

添加一个静态方法:

    static func save(_ image: UIImage) -> Observable {
        return Observable.create({ observer in

        })
    }

上面的 save 方法会返回一个 Observable, 因为实际的保存方法不会返回任何的 next 事件, 而只会是 error 或 complete.

下面就来实现上面代码中的块内容:

static func save(_ image: UIImage) -> Observable {
  return Observable.create({ observer in
    let writer = PhotoWriter(callback: { error in
      if let error = error {
        observer.onError(error)
      } else {
        observer.onCompleted()
      }
})
    UIImageWriteToSavedPhotosAlbum(image, writer,
  #selector(PhotoWriter.image(_:didFinishSavingWithError:contextInfo:)),
  nil)
    return Disposables.create()
  })
}

实际的过程是这样的: 外界调用 save 方法来保存图片, 方法会返回一个 Observable, 外界观察这个 Observable 就可以知道是保存成功还是出错.

而内部的运行是这样的: 这个类提供了一个 callback 块属性, 然后在 save 方法中建立 Observable, 在其 create 块中去创建这个 callback, callback 的参数是在保存函数的回调函数中去赋值的. 这里只是根据 error 的有无来向观察者发送 error 或 completed.

上面的这个构造十分巧妙, 需要好好学习并记住这样的构造方式.

至此, 第一部分结束.

你可能感兴趣的:(RxSwift 4 实际动手)