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可以减少重复写代码,同时也需要注意使用场景,对于可复用对象需要慎重。
相关推荐
泓博38 分钟前
KMP(Kotlin Multiplatform)改造(Android/iOS)老项目
android·ios·kotlin
Digitally38 分钟前
如何将信息从 iPhone 同步到Mac(完整步骤和示意图)
macos·ios·iphone
大猫会长44 分钟前
使用Mac自带的图像捕捉导出 iPhone 相册
ios·iphone
大熊猫侯佩9 天前
消失的它:摆脱 SwiftUI 中“嵌入视图数量不能超过 10 个”限制的秘密
swiftui·swift·apple
大熊猫侯佩9 天前
Swift 抛砖引玉:从数组访问越界想到的“可抛出错误”属性
swift·apple
大熊猫侯佩9 天前
ruby、Python 以及 Swift 语言关于 “Finally” 实现的趣谈
python·ruby·swift
二流小码农10 天前
鸿蒙开发:基于node脚本实现组件化运行
android·ios·harmonyos
依旧风轻10 天前
Domain 层完全指南(面向 iOS 开发者)
ios·domain·entity·sqi
续天续地10 天前
开箱即用的Kotlin Multiplatform 跨平台开发模板:覆盖网络/存储/UI/DI/CI工具链
ios·kotlin
minos.cpp10 天前
从厨房到代码台:用做菜思维理解iOS开发 - Swift入门篇①
ios·蓝桥杯·swift