3

I'm trying to understand how reference counting works, so I disabled ARC and wrote a simple class: (Foo.h is not pasted as it is unmodified)

Foo.m

@implementation Foo

- (instancetype)init
{
    NSLog(@"Init object");
    return [super init];
}

- (void)dealloc
{
    NSLog(@"Dealloc object");
    [super dealloc];
}

@end

Main.m

#import <Foundation/Foundation.h>
#import "Foo.h"

int main(int argc, const char * argv[]) {

    Foo *obj = [[Foo alloc] init];

    obj = nil;
    return 0;
}

Now I expect to see the dealloc object log, because the only reference to Foo object is gone, but the only message I get is the init object.

Why don't I see it? Isn't the object released when I assign obj = nil?

user2340612
  • 10,053
  • 4
  • 41
  • 66
  • Why would you expect to see `dealloc` invoked if you never `release` the object? (To get the object automatically released, make the pointer a property and assign to it with `self.obj = ...`.) – Hot Licks Feb 24 '15 at 01:06
  • Well I thought that the reference count would have been updated by itself whenever I assign the pointer to `nil`, but I was clearly wrong ;) – user2340612 Feb 24 '15 at 01:10
  • You probably were looking at examples using properties rather than "bare" pointers. – Hot Licks Feb 24 '15 at 01:10
  • Yeah probably you're right. If I use it as a "property", will it work because of the `@property (retain)`? – user2340612 Feb 24 '15 at 01:12
  • 1
    That's the general idea. If you use (retained) properties exclusively then leaks and other storage bugs are pretty rare. – Hot Licks Feb 24 '15 at 01:14
  • Ok thanks! Now I edited the code, calling `[obj release]` (so the object is deallocated and I see the log message), but then if I call a method on it (e.g. `[obj foo]`) it does not crash, but the method is called with success (it prints a log message). Is it because the memory will be cleaned at some point later in the time, so the object is still there? – user2340612 Feb 24 '15 at 01:23
  • 2
    @user2340612: Yes. `release` does not necessarily mean the object will be released immediately (even if the retain count is 0), since there are various optimizations in Cocoa to free objects from memory in an efficient way. However, once you call `release`, you should assume the object has been freed from memory and not attempt to use it! – mipadi Feb 24 '15 at 01:36
  • 1
    Your call to [obj foo] did not crash because your method did NOT attempt to access any data members of the class. You're calling a function which is resulting in a stack frame added and the contents of heap memory (a string) being output to the console. Not once is your code trying to read data from an address in the space the object existed in before release. The data member is a dangling pointer but you aren't accessing any of it, therefore you're not doing anything illegal according to the OS. If you added an ivar (like an int = 9) and attempted to print in out, I thinkyou should crash. – Joey Carson Nov 05 '15 at 01:27
  • 1
    To reiterate on my comment, when you call a member function (just as in C++) you're really just calling a static function somewhere in memory and beind the scenes, a pointer to that specific instance is being passed in that function (this is self). If you had called `[obj release]` and then `[obj foo]`, you're calling foo passing a self address that has been deleted. If you tried to read (or write to) from an address in the object's range (like an ivar), you'd access an address that you aren't allowed to (since you gave it back) and may crash the proceess. – Joey Carson Nov 05 '15 at 17:06
  • 1
    There may of course exceptions to this rule. The most notable being that a release may NOT actually relinquish the memory back to the OS immediately, as the allocator (usually malloc) is assuming something else will request memory very soon and it can just assign an address range it already owns that has been released. This could potentially differ according to platform, so take it with a grain of salt. – Joey Carson Nov 05 '15 at 17:12

2 Answers2

7

No. If you're not using ARC, the object is released when you call [obj release];. (ARC inserts these calls for you.) Setting obj to nil does nothing in terms of memory management (although it does create an object you can no longer reach!).

Basically, in Cocoa without ARC:

  • You call [obj retain] if you want to take ownership of an object. (alloc does this for you.)
  • You call [obj release] when you want to relinquish ownership of an object. release in turn calls dealloc when the object's retain count reaches 0.
  • You call [obj autorelease] when you want to relinquish ownership of an object outside of its current scope. Most commonly, this happens when you return an object from a method (and don't want to retain ownership of it).
mipadi
  • 398,885
  • 90
  • 523
  • 479
  • Wow, thanks! So in that way the only thing I get is a memory leak, isn't it? – user2340612 Feb 24 '15 at 01:07
  • 3
    @user2340612: Yep, that's a classic memory leak right there. – mipadi Feb 24 '15 at 01:08
  • 1
    For completeness you might want to describe how a property would be handled. – Hot Licks Feb 24 '15 at 01:08
  • 1
    Also maybe clarify - "the object is released when you call [obj release]" - to be more like "the object is released when the object's retain count changes to 0, calling [obj release] reduces the retain count by 1" But of course, that leads to further explanation. – KirkSpaziani Feb 24 '15 at 01:58
  • @KirkSpaziani An object is released when you call `release` (or you call `autorelease` and the pool is drained). An object is _deallocated_ when the retain count falls to zero. – Rob Feb 24 '15 at 04:48
1

A couple of additional observations to expand on mipadi's excellent answer:

  1. In non-ARC code, setting a variable to nil will not release the object. You must explicitly release/autorelease it.

    To be more precise about it, if ownership has been transferred to you (i.e. you obtain the object from a method whose name starts with either alloc, new, copy, or mutableCopy), then you're responsible for explicitly calling release/autorelease.

    Bottom line, simply setting the variable to nil is not adequate.

  2. I hesitate to mention this for fear of clouding the issue, but when dealing with properties, it is a bit different. With properties, calling the setter of a retain property will automatically retain the object for you. If you later set the property to nil, then the setter will automatically release it for you.

    Imagine a class like so:

    @interface Bar : NSObject
    @property (nonatomic, retain) Foo *foo;
    @end
    

    When you want to set the foo property, you might do the following

    Foo *f = [[Foo alloc] init];  // create Foo object with +1 retain count
    self.foo = f;                 // the `retain` property will increase retain count to +2
    [f release];                  // resolve the local strong reference, reducing retain count back to +1
    

    Or, more simply:

    self.foo = [[[Foo alloc] init] autorelease];
    

    What these do is create a Foo object with a +1 retain count. By setting the foo property, you are creating another strong reference and thus ending up with a +2 retain count. And when you then release/autorelease it, the retain count falls back to +1.

    Later, when you're done with foo and want to release it, you would simply call the setter again, this time with nil value:

    self.foo = nil;              // resolve the `retain` done by the property, reducing the retain count to +0
    

    That relieves the strong reference that the foo property maintained to the object. You do not call release yourself (on the property, at least). Obviously, when you call the setter with a nil value, the previous object is not only released, but if that was the last strong reference, the object will be deallocated for you, too.

In short, the original alloc is offset by a release/autorelease, not by setting the variable to nil. However, the setting of a retain property is resolved by setting that property to nil.


A couple of final observations.

  • I would suggest that if writing non-ARC code that you carefully review the Advanced Memory Management Programming Guide, particularly the Memory Management Policy chapter.

  • Xcode's static analyzer (shift+command+B, or "Analyze" on Xcode's "Product" menu) is excellent at identifying memory issues that plague non-ARC code. Always make sure you have absolutely no warnings from the static analyzer.

  • When using Instruments, the allocations tool has a feature called "Record Reference Counts" (see https://stackoverflow.com/a/14105056/1271826). If you're finding that some object is not getting released, this tool can be useful in diagnosing the full life cycle of the object and all of it's retain counts.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • I thought I had already left a comment, but I realized just now I didn't. Your answer is very important as it a very good companion for @mipadi's answer, especially for mentioning the behaviour of properties and the final observations – user2340612 Nov 05 '15 at 12:30