欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[unowned self] 与 [weak self]

程序员文章站 2022-03-11 20:00:16
...

转自: http://www.hangge.com/blog/cache/detail_1993.html

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

一、准备工作

1,页面创建

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

[unowned self] 与 [weak self]

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,测试一下

从主页面跳转到详情页再跳转回来。可以看到 DetailViewControllerdeinit 方法被调用,说明页面被成功释放。
[unowned self] 与 [weak self]

二、一个内存泄漏的样例

使用 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 方法没有被调用,说明页面未被释放。

[unowned self] 与 [weak self]

三、内存泄漏的解决

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 方法成功被调用,说明页面被释放。

[unowned self] 与 [weak self]

3,[unowned self] 样例

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

[unowned self] 与 [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: {
            [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] 是完全没有问题的。

[unowned self] 与 [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: {
            [unowned self] text in
            print("当前输入内容:\(String(describing: text))")
            self.label.text = text
        }).disposed(by: disposeBag)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    deinit {
        print(#file, #function)
    }
}
相关标签: unowned weak