返回 导航

Swift

hangge.com

Swift - RxSwift的使用详解42([unowned self] 与 [weak self])

作者:hangge | 2018-04-04 08:10
    Swift使用自动引用计数(ARC)来管理应用程序的内存使用,但 ARC 并不是绝对安全的。我之前也写过一篇关于 Swift 内存泄漏原因以及解决办法的文章(点击查看
    这次我专门讲讲在使用 RxSwift 时,容易出现内存泄漏的地方以及解决方法。

一、准备工作

1,页面创建

(1)这里我准备两个简单的页面:主页面(ViewController.swift)和详情页(DetailViewController.swift
(2)点击主页面的“跳转”按钮,则会打开详情页。
(3)点击详情页左上角的返回按钮,则详情页关闭(页面被释放),回到主页面。

2,页面代码

详情页代码很简单,主要是在反初始化方法(deinit)中输出一些信息,方便我们观察释放情况。
import UIKit

class DetailViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!
    
    @IBOutlet weak var label: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    deinit {
        print(#file, #function)
    }
}

3,测试一下

从主页面跳转到详情页再跳转回来。可以看到 DetailViewController deinit 方法被调用,说明页面被成功释放。

二、一个内存泄漏的样例

使用 RxSwift 时通常都是因为闭包引起的循环强引用而造成内存泄漏。

1,样例代码

这里我在详情页(DetailViewController.swift)里增加些功能:
  • 当输入框输入内容改变时,下方的文本标签会显示同样的文字,而且这些文字还会同步输出到控制台中。
  • 为了方便观察,文字显示我加了个延时。也就是说输入框输入后要过个 4 秒钟,才会显示到文本标签上。
import UIKit
import RxSwift
import RxCocoa

class DetailViewController: UIViewController {
    
    @IBOutlet weak var textField: UITextField!
    
    @IBOutlet weak var label: UILabel!
    
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        textField.rx.text.orEmpty.asDriver().drive(onNext: {
            text in
            DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
                print("当前输入内容:\(String(describing: text))")
                self.label.text = text
            }
        }).disposed(by: disposeBag)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    deinit {
        print(#file, #function)
    }
}

2,测试一下

打开详情页输入 1 后立刻返回主页面。可以看到控制台过个 4 秒仍然会输出内容,且 deinit 方法没有被调用,说明页面未被释放。

三、内存泄漏的解决

1,[weak self] 与 [unowned self] 介绍

我们只需将闭包捕获列表定义为弱引用(weak)、或者无主引用(unowned)即可解决问题,这二者的使用场景分别如下:
  • 如果捕获(比如 self)可以被设置为 nil,也就是说它可能在闭包前被销毁,那么就要将捕获定义为 weak。
  • 如果它们一直是相互引用,即同时销毁的,那么就可以将捕获定义为 unowned

2,[weak self] 样例

(1)这里我对上面的样例代码稍作修改,增加个 [weak self]
import UIKit
import RxSwift
import RxCocoa

class DetailViewController: UIViewController {
    
    @IBOutlet weak var textField: UITextField!
    
    @IBOutlet weak var label: UILabel!
    
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        textField.rx.text.orEmpty.asDriver().drive(onNext: {
            [weak self] text in
            DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
                print("当前输入内容:\(String(describing: text))")
                self?.label.text = text
            }
            
        }).disposed(by: disposeBag)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    deinit {
        print(#file, #function)
    }
}

(2)仍然按上面的操作步骤测试一下,看到 deinit 方法成功被调用,说明页面被释放。

3,[unowned self] 样例

(1)如果我们不用 [weak self] 而改用 [unowned self],返回主页面 4 秒钟后由于详情页早已被销毁,这时访问 label 将会导致异常抛出。
import UIKit
import RxSwift
import RxCocoa

class DetailViewController: UIViewController {
    
    @IBOutlet weak var textField: UITextField!
    
    @IBOutlet weak var label: UILabel!
    
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        textField.rx.text.orEmpty.asDriver().drive(onNext: {
            [unowned self] text in
            DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
                print("当前输入内容:\(String(describing: text))")
                self.label.text = text
            }
            
        }).disposed(by: disposeBag)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    deinit {
        print(#file, #function)
    }
}

(2)当然如果我们把延时去掉的话,使用 [unowned self] 是完全没有问题的。
import UIKit
import RxSwift
import RxCocoa

class DetailViewController: UIViewController {
    
    @IBOutlet weak var textField: UITextField!
    
    @IBOutlet weak var label: UILabel!
    
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        textField.rx.text.orEmpty.asDriver().drive(onNext: {
            [unowned self] text in
            print("当前输入内容:\(String(describing: text))")
            self.label.text = text
        }).disposed(by: disposeBag)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    deinit {
        print(#file, #function)
    }
}
评论

全部评论(0)

回到顶部