前言
上篇文章我们围绕着UITableViewCell有关disposed的进行了讨论,但是如果看API,你会发现在subscribe之后,其实有两种方式
- dispose()
- disposed(by:)
这里我们就说说两者的使用方式。
dispose() 和 disposed(by:)
这两种写法的主要区别在于 资源管理的方式 ,具体来说是 dispose()
和 disposed(by:)
的使用方式不同。
dispose()写法:
swift
class ViewController: UIViewController {
var disposable: Disposable?
private lazy var textField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
/// 保存这个disposable
disposable = textField.rx.text.orEmpty
.subscribe(onNext: { text in print(text) })
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
/// 开发者自行控制释放
disposable?.dispose()
}
}
特点:
- 调用即释放 :调用
dispose()
会立即释放订阅资源。这种方式适合在订阅后不需要长期持有的场景。 - 手动管理生命周期 :你需要手动调用
dispose()
来释放资源,适合短生命周期的订阅。 - 不依赖 DisposeBag :这种写法不需要
DisposeBag
,但如果你忘记调用dispose()
,可能会导致内存泄漏。
使用场景:
- 适合一次性订阅的场景,比如临时的事件处理。
disposed(by:) 写法:
swift
class ViewController: UIViewController {
let disposeBag = DisposeBag()
private lazy var textField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
/// 保存这个disposable
textField.rx.text.orEmpty
.subscribe(onNext: { text in print(text) })
.disposed(by: disposeBag)
}
}
特点:
- 自动管理生命周期 :
disposed(by:)
会将订阅添加到DisposeBag
中,DisposeBag
会在其生命周期结束时自动释放所有订阅资源。 - 更安全 :不需要手动调用
dispose()
,避免忘记释放资源导致的内存泄漏。 - 依赖 DisposeBag :需要一个
DisposeBag
,来管理订阅。
使用场景:
- 适合长期订阅的场景,比如绑定 UI 控件的事件。
- 通常用于 ViewController 或其他对象的生命周期内,订阅会在对象销毁时自动释放。
小结:
dispose()
:适合短期订阅,手动管理资源释放。有点像MRC。disposed(by:)
:适合长期订阅,自动管理资源释放,更推荐在大多数情况下使用。有点像ARC。
在实际开发中,我基本上都是使用第二种写法,因为它依赖 DisposeBag
自动管理资源,减少了手动释放的风险,代码也更简洁和安全。
当然,disposed(by:)
也有一个问题,那就是,在每一个需要使用disposed(by:)
的位置,我都需要let disposeBag = DisposeBag()
这样new一个,感觉好重复,好费力呀?
有没有更优雅的方式?这个时候就需要NSObject+Rx啦。
NSObject+Rx解析
其实NSObject+Rx的整体代码非常之简单,一共就两个文件:
HasDisposeBag.swift
swift
fileprivate var disposeBagContext: UInt8 = 0
/// each HasDisposeBag offers a unique RxSwift DisposeBag instance
public protocol HasDisposeBag: class {
/// a unique RxSwift DisposeBag instance
var disposeBag: DisposeBag { get set }
}
extension HasDisposeBag {
func synchronizedBag<T>( _ action: () -> T) -> T {
objc_sync_enter(self)
let result = action()
objc_sync_exit(self)
return result
}
public var disposeBag: DisposeBag {
get {
return synchronizedBag {
if let disposeObject = objc_getAssociatedObject(self, &disposeBagContext) as? DisposeBag {
return disposeObject
}
let disposeObject = DisposeBag()
objc_setAssociatedObject(self, &disposeBagContext, disposeObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return disposeObject
}
}
set {
synchronizedBag {
objc_setAssociatedObject(self, &disposeBagContext, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
}
NSObject+Rx
swift
fileprivate var disposeBagContext: UInt8 = 0
extension Reactive where Base: AnyObject {
func synchronizedBag<T>( _ action: () -> T) -> T {
objc_sync_enter(self.base)
let result = action()
objc_sync_exit(self.base)
return result
}
}
public extension Reactive where Base: AnyObject {
/// a unique DisposeBag that is related to the Reactive.Base instance only for Reference type
var disposeBag: DisposeBag {
get {
return synchronizedBag {
if let disposeObject = objc_getAssociatedObject(base, &disposeBagContext) as? DisposeBag {
return disposeObject
}
let disposeObject = DisposeBag()
objc_setAssociatedObject(base, &disposeBagContext, disposeObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return disposeObject
}
}
set {
synchronizedBag {
objc_setAssociatedObject(base, &disposeBagContext, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
}
主要实现原理
- 利用 Swift 的扩展(extension)和 Objective-C 的关联对象(Associated Object)机制,为 NSObject 动态添加属性。
- 每个 NSObject 实例都可以通过
rx.disposeBag
获取一个专属的 DisposeBag。 Reactive where Base: NSObject
:为所有 NSObject 子类扩展 rx 属性。disposeBag
属性通过objc_getAssociatedObject
和objc_setAssociatedObject
动态存储在对象实例上。- 第一次访问时自动创建一个新的 DisposeBag,并与对象绑定。
- 以后每次访问都返回同一个 DisposeBag。
作用
NSObject+Rx
是 RxSwift/RxCocoa 提供的一个扩展,为所有继承自 NSObject
的对象(如 UIViewController、UIView、UITableViewCell 等)自动添加了一个 disposeBag 属性,方便 Rx 绑定的资源管理。
使用示例
swift
import UIKit
import RxCocoa
import NSObject_Rx
class ViewController: UIViewController {
private lazy var textField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
/// 保存这个disposable
textField.rx.text.orEmpty
.subscribe(onNext: { text in print(text) })
.disposed(by: rx.disposeBag)
}
}
注意此时就不用let disposeBag = DisposeBag()
这样new一个disposeBag,所有继承NSObject的类此时都有一个rx.disposeBag
属性用于自动释放包了。
如果你有一个 非NSObject的类(注意是类,是类,是类,重要的事情说三遍) 也需要这个属性,写一个分类直接遵守HasDisposeBag
协议就可以了:
swift
import NSObject_Rx
class MyTool {}
extension MyTool: HasDisposeBag {}
let myTool = MyTool()
myTool.disposeBag
注意事项
- NSObject+Rx适用于生命周期与对象一致的场景(如 UIViewController、UIView)。
- 不建议在 UITableViewCell、UICollectionViewCell 这类可复用对象中使用,因为cell会复用,
rx.disposeBag
不会自动重置,可能导致订阅未释放。 - cell中应手动管理DisposeBag并在
prepareForReuse
中重置。这也是上一节我会独立写一个class BaseDisposeBagCell: UITableViewCell
去进行继承构建的原因。
总结:
- 一般情况下我们使用
disposed(by:)
进行包释放; - 配合使用NSObject+Rx可以减少重复写代码,同时也需要注意使用场景,对于可复用对象需要慎重。