Cocoa memory management for smarties, Part 3: accessors

This post is not over until Minnesota Fats says it is, or until I figure out why he played in Iowa. In the meantime, let’s talk about getters and setters. A setter is distinguished by the prefix set, while a getter is distinguished by not having the prefix get. Got it? Getters and setters come in pairs; collectively, they are commonly known as accessor methods, less commonly as American Airlines or pocket rockets.

Accessor methods provide encapsulated access to an object’s instance variablesinstancvariabls for short. If your instancvariabl is an Objective-C object (in Freudian terms, an id), your accessors should almost always look like this:

-(id) thing
{
	return [[_thing retain] autorelease];
}

-(void) setThing:(id)aThing
{
	if (_thing != aThing)
	{
		[_thing release];
		_thing = [aThing retain];
	}
}

You can copy rather than retain in setThing: if the class of aThing conforms to <NSCopying>.

It may seem redundant to [[_thing retain] autorelease] in thing, but this has an important purpose. (A special purpose, if you will, which you’ll want to use every chance you get.) Simply returning _thing in thing does not suffice, as the following code demonstrates.

1 id something = [myObject thing];
2 [myObject setThing:nil]; // Clear out the thing.
3 [something doSomething];

In line 3, your app will go boom. I’ve seen it argued (on bathroom stalls, for example) that the caller ought to [[myObject thing] retain] on line 1 because of this possibility, but that’s simply not the caller’s responsibility. It’s an implementation detail of myObject that thing returns an instancvariabl which setThing: releases. Moreover, the caller could inadvertently call setThing: by calling some other method that calls setThing: rather than by calling setThing: directly. With [[_thing retain] autorelease], thing ensures that _thing will not be deallocated in the caller’s scope (as long as the caller doesn’t do anything stupid, like call out Minnesota Fats).

No discussion of accessors is complete without talking about thread safety. In order to write thread-safe accessors, you should first drink a bottle of whiskey. This is required because writing thread-safe accessors is a fool’s errand. Don’t do it. Don’t try it. Don’t even think about. Redesign your code to avoid the need for thread-safe accessors. If you feel you must, however, then you should acquaint yourself with Technical Note TN2123, because you’ll find yourself referring to it frequently. It’ll be like a nightmare, just getting (and setting) worse and worse. But at least the salad dressing will be good.

6 Responses to “Cocoa memory management for smarties, Part 3: accessors”

  1. Michal says:

    Never read such funny and still true intro to cocoa’s memory management. And I’ve read a bunch of them since I started with objective-c in middle 90s.

  2. Andrew says:

    Admittedly I’m still a bit of a Cocoa beginner, but why wouldn’t changing [_thing release] to [_thing autorelease] in the setter suffice?

  3. Jeff says:

    Michal, thanks!

    Andrew, that’s a good question. Suppose that on line 2 you have [updater updateThings] instead of [myThing setThing:nil], where updateThings contains the following code:

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    ...
    [[allThings objectAtIndex:i] setThing:nil];
    ...
    [pool release];
    

    Since the setter’s [_thing autorelease] occurs within the context of this nested pool, _thing could still be deallocated before line 3.

  4. Dave says:

    This code:
    -(void) setThing:(id)aThing
    {
    if (_thing != aThing)
    {
    [_thing release];
    _thing = [aThing retain];
    }
    }

    Can be easily rewritten as:
    -(void) setThing:(id)aThing {
    [aThing retain];
    [_thing release];
    _thing = aThing;
    }

  5. Scott says:

    I have read at some point that in a multithreaded app it is possible that one thread can actually return a released object if it hits at the wrong time:
    For example in your setter….

    if (_thing != aThing)
    {
    [_thing release];
    // if your getter hit here on a different thread — you have problems.
    _thing = [aThing retain];
    }

    A safer approach is to release the old object AFTER the the pointer to the new object has been assigned. use a local pointer to keep the reference of the ivar so it can be released afterwards

    -(void) setThing:(id)aThing{
    if (_thing!= aThing){
    id oldThing = _thing;
    _thing = [aThing retain];
    [oldThing release];
    }
    }

  6. Jeff says:

    Dave, it’s true that both approaches would have the same result. The equality check is a minor optimization, because pointer comparison is faster than Objective-C messages. In most cases, though, it probably doesn’t matter. If performance is an issue, there are various ways of optimizing the setter.

    Scott, it should be clear from my post that the accessors were never intended to be thread-safe.