0
class MyClass {
    var someProperty = 0

    deinit {
        // never gets called
    }

    func doSomething() {
        DispatchQueue.global().async { [weak self] in
            Thread.sleep(forTimeInterval: 3600)
            self?.someProperty = 123
        }
    }
}

class MyViewController: UIViewController {
    var myClass: MyClass?

    deinit {
        // MyViewController gets properly deallocated
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        myClass = MyClass()
        myClass?.doSomething()
    }
}

When running the above code, MyClass never gets deallocated even when MyViewController is popped of the navigation stack or gets dismissed.

However, if I move the implementation of MyClass directly to MyViewController, everything works as expected:

class MyViewController: UIViewController {
    var someProperty = 0

    deinit {
        // MyViewController still gets properly deallocated
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        DispatchQueue.global().async { [weak self] in
            Thread.sleep(forTimeInterval: 3600)
            self?.someProperty = 123
        }
    }
}

I used the Debug Memory Graph to figure out what is still keeping a reference to the instance of MyClass and this is what I get:

enter image description here

Why is there still a reference? What am I missing here?

UPDATE

So I was trying to figure out why it works with MyViewController but not when having another instance in between. Seems like inheriting from NSObject makes a difference. When I inherit MyClass from NSObject, deinit is called and once the long operation is finished, self is then correctly set to nil.

The question now is, in what way are capture lists in Swift related to NSObject?

bcause
  • 1,303
  • 12
  • 29
  • 1
    Have you tried to make the myClass var weak? Simply add a weak before var. –  Apr 21 '17 at 10:04
  • 1
    @Maik639 Well, if I set it to weak then it would get deallocated right away and MyClass.doSomething would never get called. – bcause Apr 21 '17 at 10:12
  • Just guessing - you could perhaps try setting the variable `myClass` to `nil` in the view controller's `deinit`. Also, if the background task is still running, the var not being deallocated is expected. You are running it for 1 hour, try reducing the time interval and re-test. – Mundi Apr 21 '17 at 10:23
  • @Mundi That shouldn't make a difference either since as my samples demonstrate, MyViewController does get deallocated, so property 'myClass' will not exist anymore. And regarding the long operation, of course, if the task finishes it gets properly deallocated. But my point is that it should get deallocated as soon as MyViewController is gone, since there shouldn't be any strong references anymore to MyClass EXCEPT for the closure, but that one uses a capture list which supposed to tackle exactly those kind of scenarios. As mentioned, it does work if the closure is moved to MyViewController. – bcause Apr 21 '17 at 10:28
  • Yeah. Because you have no strong reference left. So you have to use a strong-self within your thread. That should work. However the best solution would probably be a completion-block and then set myClass to nil after the task was finished. –  Apr 21 '17 at 10:33
  • Dismissed viewController is not always deallocated. At least I had lastly such a case. It would be rather super weird if one of super core functionality pieces of Swift were broken as you describe here. – Adas Lesniak Nov 12 '18 at 12:54

0 Answers0