Do i need to write weak self in Grand Central Dispatch?

Here we had a debate: do we need to write [weak self] in GCD?

One says:
- [weak self] you need to write everywhere!
The second says:
- No, even if you do not write [weak self] inside DispatchQueue, there will be no memory leak.

Instead of understanding, it’s easier to write a couple of lines. It’s harder to write a post about it.
So, we will create a UIViewController in which the method in DispatchQueue will be called five seconds after 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")
    }
}

This ViewController will push from another ViewController. And the bottom line is that in these five seconds, before calling our method, we must remove this ViewController from the UINavigationController stack. That is, just push back.

After starting in the console we see:
viewDidLoad
deinit

That is, after creating our ViewController-a, viewDidLoad was called . Then, after pushing back, our ViewController was deleted from memory and called deinit . And our method in DispatchQueue was not called from the 19th line, because at that moment our ViewController-a no longer exists, self is nil.

Now let's see what happens if we remove [weak self] from DispatchQueue and leave it like this.

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")
    }
}

Console:
viewDidLoad
method
deinit

Called viewDidLoad. After five seconds, our method is executed and only then the ViewController is de-unified. That is, after pushing back, our ViewController lives until the method is executed and only then is freed. But there is no memory leak! Because in the end he left.

And what will happen if some closure is passed to DispatchQueue. Like this:

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

Called viewDidLoad. Then the ViewController is removed. And after five seconds, our closure is executed. That is, it doesn’t matter to him whether the ViewController is alive or not. It has no link to our ViewController. He will be called in any way.

And how should it be that a leak occurs? Our closure needs to call the ViewController-a method, that is, have a link to it.

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")
    }
}

In the console:
viewDidLoad
method

Here, in the end, deinit did not invoke and we got a memory leak. And to get rid of it, you just need to write [weak self] in closure.

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")
    }
}

Console:
viewDidLoad
deinit

Summary

It doesn’t matter whether or not to write [weak self] to GCD, there will be no memory leak. But you need to know that they have different behaviors. In the first case, what is inside Dispatch will not be executed. And in the second - it will be executed, but before its execution the ViewController will live.

Also popular now: