Deinit never called
Asked Answered
C

13

65

I'm creating a ViewController object an pushing it to a navigation controller. When the object is being popped from the stack - it is not being release and Deinit is not being called. What can be the reason for that?

Here's the code that pushes:

self.navigationController?.pushViewController(CustomViewController(), animated: true)

And here's the code that pops:

 self.navigationController?.popViewControllerAnimated(true)
Caia answered 17/11, 2014 at 11:11 Comment(7)
Instead of deinit you have to use dealloc to clean up all allocated resources and like this.Heiress
I'm using Swift - so Deinit should be used - no?Caia
Maybe you have strong reference cycles in the CustomViewController. Can you provide more information about your view controller?Entrust
It's nothing special. It holds a TableViewController (it's the datasource and delegate) and a UISearchDisplayControllerCaia
But, still @godmoney, seeing is believing. We don't know what your datasource/delegate methods are doing and the devil is in the details as they say.Jolinejoliotcurie
I am experiencing the same issue. Can a strong reference cycle be ruled out if none show up in the leaks instrument?Isocracy
If you are using breakpoints to determine that deinit is never called, be aware that breakpoints in deinit behave differently than anywhere else in your code! Breakpoints will only work in deinit if there is an executable line of code ahead of them.Recrimination
P
77

I had similar problem. I added empty deinit method to my class and added breakpoint:

deinit {

}

As result it's never called.
As soon as I add some code to the body it started working as expected:

deinit {
    print("deinit called")
}

So be sure that your deinit method isn't empty.
PS. I am using Swift 2, Xcode 7.1

Picklock answered 7/11, 2015 at 7:30 Comment(4)
I had the exact same problem as you. I found out that this happens because breakpoints will only work in deinit if there is an executable line of code ahead of them.Recrimination
That's pretty strangeBucksaw
@Duan It is a part of compiler optimisation wherein the empty methods without implementation are skipped in a final binary.Diamond
To avoid this issue without writing a deinit method you can create a symbolic breakpoint on ClassName.deinit. There's a good explanation how to do it here: bignerdranch.com/blog/xcode-breakpoint-wizardryKyungkyushu
J
58

Do any of your classes, or properties they contain make a reference to the view controller you've popped?

If your UIViewController has created an instance of an object, which in turn makes a 'strong' reference to that view controller (e.g. a reference that's not explicitly declared 'weak' or 'unowned'), and your view controller keeps a strong reference to that object as well, neither will be deallocated. That's called a strong reference cycle, documented here (a must read for serious Swift developers):

The Swift Programming Language (Swift 3.0.1): Automatic Reference Counting

Closures are a more insidious case where you can get into trouble.

One thing you might try as an experiment is pushing the controller and popping it before you do anything in viewDidLoad or in the initialization, and see if the deinit method is being called. If it is, then you should be able to incrementally discover what you're doing that's leading to the symptom you're seeing.

Another thing that can thwart diagnosis (as other answers have pointed out), which I learned the hard way, is that the debugger breakpoint won't be taken for deinit() if the deinit method contains no executable statements, because the OS or compiler optimizes the deinit invocation away if it's empty, so at least put a print statement there if you want to verify deinit() is getting called.

Jolinejoliotcurie answered 28/1, 2015 at 20:51 Comment(2)
Agreed. The ARC reading was very helpful as well. I was bitten by referring to "self" in a closure and not adding a specific capture list of "[unowned self]" . Thanks!Villous
This led me to making sure my delegate was declared weak. Something I usually do but missed on this occassionPredetermine
C
18

I was using this tutorial Custom TabBar with a custom seque on the given page. The memory problem was due a subview which had a strong reference the the parent viewcontroller.

class WBMNewsFeedV: UIView
{
   var parentVC:WBMHomeTabVC!
}

WBMNewsFeedV - subclass

parentVC:WBMHomeTabVC - parent class ViewController

I changed it to :

class WBMNewsFeedV: UIView
{
    weak var parentVC:WBMHomeTabVC!
}

So, the strong reference was nested inside a subviews and not visible at first. Hope this helps anyone. After the change deinit was always called in

WBMHomeTabVC

Camilia answered 20/5, 2015 at 8:54 Comment(2)
Had same problem. To find the references I searched for self in my ?ViewController finding all places where I have passed it as reference to be stored in a delegate variable. Apparently one of three was not set as weakBlakeblakelee
@Blakeblakelee that was a good suggestion for how to find the culprit referencesEpicarp
I
13

I had the same issue when the NotificationCenter was holding a strong reference to the presented view controlled and thus it was never getting released. So I had to add [weak self] to the block like this:

(in the viewDidLoad)

NotificationCenter.default.addObserver(forName: .showFoo, object: nil, 
  queue: OperationQueue.main) { [weak self] (notification) in
    if let foo = notification.userInfo?["foo"] as? Foo {
            self?.afooButton!.setImage(UIImage(named: "foo"), for: .normal)
    }
}
Interferon answered 3/11, 2016 at 3:24 Comment(0)
E
12

add some line code of in deinit. If You put breakpoint at Empty deinit ,compiler will not stop you there put this:

deinit {
print("any thing")
}

It will work ;)

Engorge answered 8/12, 2016 at 13:20 Comment(0)
C
7

I had a timer in my view controller, running every minute to update a label. I put a call in deinit to invalidate the timer, which I'm pretty sure is what I've always done in Objective-C (in dealloc) and it's worked. But it seems a little different in Swift so I moved the time creation/invalidation code to viewWillAppear/viewWillDisappear (which makes more sense really) and everything seems fine now!

Comminute answered 26/1, 2016 at 9:0 Comment(0)
I
6

I just ran into a similar issue. My problem appears to have been caused by a strong reference to a control that had delegate assignment not specified as 'weak' with a class type protocol.

Isocracy answered 5/5, 2015 at 14:45 Comment(2)
Ignoring "me too" part this actually may be an answer - if you are reviewing and are swift expert please see if "strong reference ... not 'weak'..." could be possible reason for problem in the question.Goethite
Winner! Had an @objc protocol and forgot to mark is as weak. Thanks for the reminder.Episodic
I
5

I had same issue what I found is I didn't make weak reference for my other class delegate

protocol SomeClassDelegate : AnyObject {
    func someClassDelegateMethod(<param>)
}

class SomeClass: NSObject {

    // Make delegate weak reference 
    weak var delegate:InfractionDataManagerDelegate? = nil
    < some code >
}

now deinit is being called on my implementation class.

Intranuclear answered 21/6, 2019 at 7:2 Comment(0)
J
3

Just to add an edge case which I found very difficult to detect:

If you allocate any UnsafeMutablePointers and provide self (the UIViewController) as pointee inside of your view controller, make sure to call pointer.deinitialize(count:), not just pointer.deallocate().

Otherwise, the reference to self will remain and the UIViewController will not deinitialize.

Jeffcott answered 8/12, 2018 at 18:39 Comment(0)
L
2

I faced the same Problem. In my case a never ending cycle of UIView.animateWithDuration... holds the ViewController in Memory and blocks the call of deinit. If you use something like this you have to stop it first bevor you remove the ViewController. Hope that helps.

Leaves answered 4/11, 2015 at 21:41 Comment(0)
A
1

I had a similar issue: I had some UIViews as arranged subviews of a *stackview" contained in a scrollview in a detail view controller. It worked when I removed the UIViews from the stackview on back button tap (willMove(toParent parent: UIViewController?)). Another thing to check is when in your view controller you have something like: let session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue()) (that self could in some cases prevent a detail view controller to be deallocated when you click Back and move to the master)

Apperceive answered 3/3, 2019 at 12:0 Comment(0)
E
0

First of all make sure to define deinit

deinit {
    print("OverlayView deinit")
}

Use Xcode's object graph to check number of instances being created and if they are not getting deallocated then they will keep growing. I was creating property of another ViewController on top of the file so i move it and place it in the scope it was being used that solved my problem. And its deinit started calling.

Moreover i was using a uiview property to show a overlay that need to be accessed from my viewcontroller at some places i make it optional and set it nil when after calling removefromsuperview on it.

var overlay: OverlayView?
overlay = nil

If still dealloc not calling then there could be a retain cycle issue as described above in that case you will have to check another viewcontroller(B) too if its calling back this controller(A) then set one of them as a weak variable.

Ellerd answered 15/11, 2018 at 6:11 Comment(0)
Q
0

the most helping material that I could find in this problem is link

But for me the problem was typealias

Quinquepartite answered 1/11, 2019 at 7:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.