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可以减少重复写代码,同时也需要注意使用场景,对于可复用对象需要慎重。
相关推荐
无知的前端3 小时前
Flutter开发,GetX框架路由相关详细示例
android·flutter·ios
大熊猫侯佩4 小时前
iOS 18 中全新 SwiftData 重装升级,其中一个功能保证你们“爱不释手”
数据库·ios·swift
大熊猫侯佩4 小时前
SwiftUI 6.0(iOS 18)新容器视图修改器漫谈
ios·swiftui·wwdc
Digitally5 小时前
如何将 iPhone 中的短信导出为 PDF
ios·pdf·iphone
帅次1 天前
Flutter Container 组件详解
android·flutter·ios·小程序·kotlin·iphone·xcode
SoaringHeart1 天前
SwiftUI组件封装:仿 Flutter 原生组件 Wrap实现
ios·swiftui
YungFan1 天前
SwiftUI-自定义与扩展
swiftui·swift
I烟雨云渊T1 天前
iOS 抖音首页头部滑动标签的实现
ios
十月ooOO1 天前
uniapp 云打包 iOS 应用上传到 app store 商店的过程
ios·uni-app