iOS Dealloc and Destructor confusion

UCLEUCLE USMember ✭✭
edited June 2015 in Xamarin.iOS

When does the the c# destructor get called for NSObjects? I am guessing it gets called when the NSObject dealloc method gets called, is this correct?

Posts

  • rschmidtrschmidt USMember ✭✭

    C# doesn't really have destructors, it has finalizers, which are sometimes called destructors but are not much alike the destructors in, say, C++. An object's finalizer gets called before it gets collected by the garbage collector. You can't predict when that will happen.

    You can only predict (maybe) when an object becomes eligible to be collected by the garbage collector. Maybe it will be collected a few seconds thereafter, or maybe it will take an hour, or maybe your app will crash and it will never get collected (You can force a garbage collection to collect all eligible objects on the spot, but this is rarely a good idea). The only reason why I can imagine you might want to make such predictions is if you're trying to understand the reason behind a memory leak.

    For a managed C# object to be eligible for collection it must be referenced from no managed root and also from no native root (represented by a GCHandle object). For NSObject subclasses, when you do something to cause your native NSObject to get retained (for example, add it as a subview of another view), a GCHandle is created to hold your managed NSObject instance in memory. If no such handle exists, and no reference from a managed root exists, the managed object becomes eligible for collection.

    In general you should not use finalizers often, if at all. They are for cleaning up unmanaged resources.

    Watch this video if you want to understand this all as well as possible: http://usergroup.tv/videos/xamarin-ios-deep-dive

  • UCLEUCLE USMember ✭✭

    Very helpful thanks!

  • adamkempadamkemp USInsider, Developer Group Leader mod

    You can also read more about finalizers and IDisposable here. All NSObjects use this to coordinate between the native object and the managed wrapper.

  • UCLEUCLE USMember ✭✭
    edited June 2015

    The articles that have been posted here have been helpful, and I have made the following assumptions.

    1. Calling GC.Collect() will cause the garbage collector to get called for all generations.
    2. That Dispose and finalizer will always be called when the object get collected; unless finalizer is suppressed (CG.SuppressFinalize(object))
    3. That NSObject and subclasses does not suppressed the finalizer being called, and generally this is not the case.
    4. That if a NSObject finalizer and or Dispose does not get called (when garbage collector is called), that object has not been collected. (most likely a retain cycle)

    Are these correct?

    I have been having problems understanding memory issues, and the profiler (xamarin and instruments) don't seem to be consistence and are confusing me. I created a test project to help me figure out things https://github.com/mgchris/XamarinMemoryTesting, if anyone wants to look at it.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Calling GC.Collect() will cause the garbage collector to get called for all generations.

    I believe this is true, but keep in mind that GC is not the same as finalization. The GC finds objects that are candidates for collection, but any of those that have finalizers will not be collected at that time. Instead, they go on the finalizer queue, which keeps the object alive until the finalizer runs. The runtime typically (and definitely in the case of mono) has its own thread where it runs the finalizer. That thread pulls objects off the queue as it can, and only once that object has had its finalizer run does it again become a candidate for collection. Therefore when an object has a finalizer it has to go through the GC twice.

    That Dispose and finalizer will always be called when the object get collected; unless finalizer is suppressed (CG.SuppressFinalize(object))

    This isn't quite right. First, the GC doesn't by itself know anything about Dispose. Dispose() is only called when some code explicitly calls it. Dispose(bool) is typically called both by Dispose() (with true) and by the finalizer (with false). So if you follow that pattern (which NSObject does) then Dispose(bool) should get called at some point as long as that object is either disposed explicitly or is finalized.

    GC.SuppressFinalize doesn't have anything to do with Dispose. What that does is prevent the finalizer from running. That is typically used in Dispose(), which usually looks like this:

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
    

    That doesn't stop Dispose from being called. It stops the finalizer from being called, and the point of doing that is that the only thing the finalizer does is call Dispose(bool), which we just called already. So there's no longer a need for a finalizer, and this prevents the object from needing to go into the finalizer queue, which means it can be collected the first time it's a candidate instead of having to wait.

    That NSObject and subclasses does not suppressed the finalizer being called, and generally this is not the case.

    As shown above, it does suppress finalization in the Dispose() method, and that generally is the case. However, this happens only in the case where someone called Dispose() explicitly.

    That if a NSObject finalizer and or Dispose does not get called (when garbage collector is called), that object has not been collected. (most likely a retain cycle)

    GC.Collect does not immediately call any finalizers, and again Dispose is never called directly by the runtime. The finalizer calls Dispose(bool) (because it was written to do so), and the finalizer runs asynchronously in a background thread. So calling GC.Collect does not guarantee that objects have Dispose called or their finalizers called. It just puts objects that have finalizers in a queue, and eventually their finalizers will be called. You can wait for all pending objects to be collected like this:

    GC.Collect();
    GC.WaitForPendingFinalizers();
    

    That will wait for any objects identified as candidates for collection that have finalizers to actually run their finalizers. However, it's not generally a good idea to rely on this for multiple reasons. To understand this better read this.

  • UCLEUCLE USMember ✭✭
    edited June 2015

    Thanks! This manual memory guy is having a hard time coming to garbage collecting world. Thanks for helping me out and for the links!

  • rschmidtrschmidt USMember ✭✭

    @UCLE By the way it might help you in your experimentation to know about WeakReferences:

    https://msdn.microsoft.com/en-us/library/system.weakreference(v=vs.110).aspx

    You can use the IsAlive property to find out for sure whether an object has been collected or not.

    Unfortunately you can't always trust the tools when it comes to memory leaks. It's pretty hard to avoid leaks in big Xamarin apps but it can be done if you know what to look for... and have time.

Sign In or Register to comment.