Хабрахабр

[Из песочницы] Нужно ли писать weak self в Grand Central Dispatch?

Тут у нас возник спор: нужно ли писать [weak self] в GCD?

Один говорит:
– [weak self] нужно писать везде!
Второй говорит:
– Нет, даже если не писать [weak self] внутри DispatchQueue, утечки памяти не будет.

Тяжелее об этом написать пост.
Итак, мы создадим UIViewController, в котором будет вызываться метод в DispatchQueue через пять секунд после viewDidLoad. Вместо того, чтобы разбираться, легче написать пару строк.

class SecondViewController: UIViewController ) } func method() { print("method") } deinit { print("deinit") } }

Этот ViewController будет пушиться с другого ViewController-а. А сама суть в том, что за эти пять секунд, до вызова нашего метода, мы должны удалить этот ViewController со стэка UINavigationController. То есть, просто нажать назад.

После запуска в консоли видим:

viewDidLoad
deinit

То есть, после создания нашего ViewController-a вызвался viewDidLoad. Затем, после нажатия назад, наш ViewController удалился из памяти и вызвался deinit. А наш метод в DispatchQueue не вызвался с 19-ой строки, потому что в этот момент нашего ViewController-a уже не существует, self равно nil.

Теперь посмотрим, что будет если мы уберем [weak self] из DispatchQueue и оставим его так.

class SecondViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() print("viewDidLoad") DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: { self.method() }) } func method() { print("method") } deinit { print("deinit") } }

Консоль:

viewDidLoad
method
deinit

Вызывается viewDidLoad. После пяти секунд исполняется наш метод и только потом ViewController деинитится. То есть, после нажатия назад, наш ViewController живет, пока не исполнится метод и только потом освобождается. Но утечки памяти не происходит! Потому что в итоге он удалился.

Вот так: А что будет, если в DispatchQueue передать какой-нибудь closure.

class SecondViewController: UIViewController { var closure: (() -> Void)? override func viewDidLoad() { super.viewDidLoad() print("viewDidLoad") closure = { print("closure") } DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: closure!) } func method() { print("method") } deinit { print("deinit") } }

Output:

viewDidLoad
deinit
closure

Вызывается viewDidLoad. Затем удаляется ViewController. А после пяти секунд исполняется наш closure. То есть ему не важно жив ViewController или нет. У него нету ссылки на наш ViewController. Он по-любому вызовется.

Нужно, чтобы наш closure вызывал метод ViewController-a, то есть имел на него ссылку. А как должно быть, чтобы произошла утечка?

class SecondViewController: UIViewController { var closure: (() -> Void)? override func viewDidLoad() { super.viewDidLoad() print("viewDidLoad") closure = { self.method() } DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: closure!) } func method() { print("method") } deinit { print("deinit") } }

В консоли:

viewDidLoad
method

Вот, в итоге deinit не вызвался и мы получили memory leak. А чтобы избавиться от него, нужно всего лишь в closure написать [weak self].

class SecondViewController: UIViewController { var closure: (() -> Void)? override func viewDidLoad() { super.viewDidLoad() print("viewDidLoad") closure = { [weak self] in self?.method() } DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: closure!) } func method() { print("method") } deinit { print("deinit") } }

Консоль:

viewDidLoad
deinit

Итог

Но надо знать, что у них поведение разное. Не важно, писать [weak self] в GCD или нет, утечки памяти не будет. А во втором — исполнится, но до его исполнения ViewController будет жить. В первом случае, то, что внутри Dispatch-a не исполнится.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть