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<UIImage>()
    var selPhotos: Observable<UIImage> {
        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="<full path to>/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<Void> {
        return Observable.create({ observer in

        })
    }

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

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

static func save(_ image: UIImage) -> Observable<Void> {
  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.

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

至此, 第一部分结束.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容