Один говорит:
– [weak self] нужно писать везде!
Второй говорит:
– Нет, даже если не писать [weak self] внутри DispatchQueue, утечки памяти не будет.
Вместо того, чтобы разбираться, легче написать пару строк. Тяжелее об этом написать пост.
Итак, мы создадим UIViewController, в котором будет вызываться метод в DispatchQueue через пять секунд после viewDidLoad.
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad")
DispatchQueue.main.asyncAfter(deadline: .now()+5, execute: { [weak self] in
self?.method()
})
}
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 или нет, утечки памяти не будет. Но надо знать, что у них поведение разное. В первом случае, то, что внутри Dispatch-a не исполнится. А во втором — исполнится, но до его исполнения ViewController будет жить.
Комментарии (7)
NishchebrodKolya2
19.05.2019 19:18+5Статья о том, что вы не знаете swift?
LLIo6oH
20.05.2019 13:55+1Как же меня раздражают вот такие напыщенные типы как ты. Ну что сразу выпендриваться. Человек поделился информацией, кому-то она будет полезна. Ну что ты из себя бога-то строишь? Ну укажи на проблемы статьи или покажи «вот это и это не верно, нужно вот так».
Слышь, Иван Гай, критикуешь — предлагай! (с)NishchebrodKolya2
20.05.2019 14:38-2Я высказал своё мнение, на большее статья не тянет. А то что вам не нравится, лучше держите при себе, так взрослые люди поступают. И не надо мне тут тыкать, лучше идите кабеля пальцем в сраку тыкнете.
NishchebrodKolya2
20.05.2019 13:15В карму гадят похоже знатоки swift. По этому вопрос к ним, и к автору этой замечательной статьи.
Если код переписать как-то так, что изменится?
extension Thread { func perform(_ block: @escaping @convention(block) () -> Void) { perform(#selector(run(_:)), on: self, with: block, waitUntilDone: false) } @objc private func run(_ closure: @escaping () -> Void) { closure() } } class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() Thread.detachNewThread { Thread.sleep(forTimeInterval: 5) Thread.main.perform { self.method() } } } func method() { print("method") } }
В «внутри» DispatchQueue будте утечка памяти или нет?saniaxxx
20.05.2019 16:40Не вижу тут цикла удержания, соотвественно и утечки не будет.
Но я не знаток свифта
Mersenne
20.05.2019 13:55В первом случае, то, что внутри Dispatch-a не исполнится
Это не так. Исполнится в любом случае. Но c weak ссылка будет уже убита, и выражение «self?.whatever()» ничего не сделает.
GCD довольно безопасен в этом плане, там тяжело получить утечку в привычном понимании. После выполнения замыкания (или блока, если хотите), GCD освобождает ссылку него. Хотите «придержать» self до момента выполнения кода? Не пишите weak, GCD подержит замыкание, а замыкание подержит self. Наоборот, хотите избежать выполнения лишней логики и побыстрее освободить память? weak в помощь. Удобно.
Daniyar94
Отличная статья на эту тему: https://analytics.twitter.com/mob_idsync_click?slug=HSpjuxigdu&idb=AAAAEIBre9gaOc4L46kKEH_nlv-oULcndJRYG4I7xgbV_ngMgpTUF-V8J8GZ0VcGiW_djTkrgpq2wm8e6GYM7wfuaUF-iWCt2pQ3ZblLVze4gQXyW_FYH8WjSL0PPqkZoL1tuL4nNNw9lobLCb-8Q-kSSmSkAJiS&ad_tracking=false&tailored_ads=false
Покрывает все аспекты memory management