RxSwift:dispose() 和 disposed(by:) 以及NSObject+Rx

前言

上篇文章我们围绕着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()
    }
}

特点:

  1. 调用即释放 :调用 dispose() 会立即释放订阅资源。这种方式适合在订阅后不需要长期持有的场景。
  2. 手动管理生命周期 :你需要手动调用 dispose() 来释放资源,适合短生命周期的订阅。
  3. 不依赖 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)
    }
}

特点:

  1. 自动管理生命周期disposed(by:) 会将订阅添加到 DisposeBag 中,DisposeBag 会在其生命周期结束时自动释放所有订阅资源。
  2. 更安全 :不需要手动调用 dispose(),避免忘记释放资源导致的内存泄漏。
  3. 依赖 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_getAssociatedObjectobjc_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可以减少重复写代码,同时也需要注意使用场景,对于可复用对象需要慎重。
相关推荐
他们都不看好你,偏偏你最不争气1 小时前
iOS —— 天气预报仿写总结
ios
白玉cfc7 小时前
【iOS】网易云仿写
ui·ios·objective-c
归辞...10 小时前
「iOS」——内存五大分区
macos·ios·cocoa
HX43611 小时前
MP - List (not just list)
android·ios·全栈
忆江南15 小时前
NSProxy是啥,用来干嘛的
ios
忆江南15 小时前
dyld
ios
归辞...1 天前
「iOS」——GCD其他方法详解
macos·ios·cocoa
游戏开发爱好者81 天前
没有 Mac,如何上架 iOS App?多项目复用与流程标准化实战分享
android·ios·小程序·https·uni-app·iphone·webview
Digitally2 天前
如何将 iPhone 备份到 Mac/MacBook
macos·ios·iphone
songgeb2 天前
Concurrency in Swift学习笔记-初识
ios·swift