[unowned self] 与 [weak self]

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

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

从主页面跳转到详情页再跳转回来。可以看到 DetailViewControllerdeinit 方法被调用,说明页面被成功释放。
这里写图片描述

二、一个内存泄漏的样例

使用 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)
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值