mikeash.com: just this guy, you know?

Posted at 2009-11-27 16:32 | RSS feed (Full text feed) | Blog Index
Next article: Friday Q&A 2009-12-04: Building Standalone iPhone Web Apps
Previous article: Friday Q&A 2009-11-20: Probing Cocoa With PyObjC
Tags: accessors cocoa objectivec
Friday Q&A 2009-11-27: Using Accessors in Init and Dealloc
by Mike Ash  

It's Black Friday, and that means it's time for another Friday Q&A. Today I'm going to talk about the use of accessors in Objective-C init/dealloc methods, a topic suggested by Jon Trainer.

Introduction
There has been a change in the Cocoa community in the past few years, where the use of accessors in init/dealloc is frowned upon, recommend against, and outright considered to be wrong. It is much better, they say, to directly access instance variables instead. In other words, the recommendation is to write code like this:

    - (id)initWithWhatever: (id)whatever
    {
        if((self = [self init]))
        {
            _whatever = [whatever retain];
        }
        return self;
    }
    
    - (void)dealloc
    {
        [_whatever release];
        
        [super dealloc];
    }
The alternative is to use accessors, like this:
    - (id)initWithWhatever: (id)whatever
    {
        if((self = [self init]))
        {
            [self setWhatever: whatever];
        }
        return self;
    }
    
    - (void)dealloc
    {
        [self setWhatever: nil];
        
        [super dealloc];
    }
Pros of Accessors
The pros of using accessors in init/dealloc are pretty much the same as the pros of using them anywhere else. They decouple the code from your implementation, and in particular help you with memory management. How many times have you accidentally written code like this?
    - (id)init
    {
        _ivar = [NSArray arrayWithObjects:...];
        return self;
    }
I expect the answer will vary quite a bit (I haven't made this particular error in quite a long time) but using an accessor will make sure you don't make this mistake:
    - (id)init
    {
        [self setIvar: [NSArray arrayWithObjects:...]];
        return self;
    }
Furthermore, you might have ancillary state, like caches, summaries, etc. that need to be set up and torn down when the object value changes. Correct use of accessors can ensure that all of this happens as it needs to when the object is created and destroyed without duplicate code.

Cons of Accessors
The downside to using accessors in this way can be summed up in one short sentence: accessors can have side effects. Sometimes these side effects are undesirable for init/dealloc.

When writing a setter, it needs to behave correctly if you're going to call it from init/dealloc. This means dealing with a partially constructed object.

Worse, if you ever override a setter in a subclass, you need to write it to handle the case where the superclass is using that setter to initialize or destroy its ivars. For example, this bit of innocuous-looking code is potentially dangerous:

    - (void)setSomeObj: (id)obj
    {
        [anotherObj notifySomething];
        [super setSomeObj: obj];
    }
    
    - (void)dealloc
    {
        [anotherObj release];
        [super dealloc];
    }
If the superclass uses the accessor to destroy someObj, then the override will execute after dealloc has already executed, causing the override to access a dangling reference to anotherObj, probably causing a nice crash.

It's not hard to fix this code to handle the situation gracefully. Simply assign anotherObj = nil after releasing it in dealloc, and everything works again. In general it's not difficult to make sure that your overrides behave properly, but if you're going to use accessors like this then you must remember to, and that is the difficult part.

Key-Value Observing
A topic which is frequently brought up in these discussions is key-value observing, because KVO is a common way for accessors to have side effects. Like other cases, if KVO side effects are triggered when the object is partially initialized or destroyed, and that code isn't written to tolerate such an object, bad things will occur. I personally think that it's mostly a red herring.

The reason it's mostly a red herring is because 99% of the time, KVO is not set up on an object until after it's already fully initialized, and is ceased before an object is destroyed. It is conceivable for KVO to be used such that it activates earlier or terminates later, but it's unlikely in practice.

It's not possible for outside code to do anything with your object until it's fully initialized, unless your initializer itself is passing pointers around to outside objects. And likewise, it's not possible for outside code to keep a KVO reference to your object as it's executing its dealloc method, because it's too late for it to remove it, unless your dealloc triggers something that allows it to do so. Superclass code does execute in these timeframes, but superclass code is extremely unlikely to do anything to cause external objects to observe properties that the superclass itself doesn't even have.

Conclusion
Now you know the pros and cons; should you use accessors in init and dealloc? In my opinion, it can go either way. The advantages aren't generally that great. The downsides are minor. I don't use accessors for the vast majority of my instance variables, but there are certain cases where the advantages become significant because I'm doing something special with an ivar, and in that case I don't hesitate to let the accessors save me some headache.

That's it for this week. Come back next week for another round. If you have a topic you would like to see covered here, send it in! Friday Q&A is driven by your suggestions, and the more I receive, the better topics you get to read about.

Did you enjoy this article? I'm selling a whole book full of them. It's available for iBooks and Kindle, plus a direct download in PDF and ePub format. It's also available in paper for the old-fashioned. Click here for more information.

Comments:

foobaz at 2009-11-27 17:08:43:
I use accessors in init/dealloc and haven't had any problems doing so.

mfazekas at 2009-11-27 20:22:54:
I'm glad to see an article that isn't against using accessors in init/dealloc.

IMHO the subclassing argument is a red herring as well. What it's saying is that you should complicate your classes, so that it's easier to subclass it. This misses a few important points:
1. You very rarely subclass
2. If you follow this logic you shouldn't call any of the methods from init/dealloc as someone might override them...
3. Subclassing is the tightest coupling possible between classes. It's hard to get right, and you should consider a lot of issues when overriding methods. So using ivars directly instead of accessors will not make subclassing significantly easier, just a tiny bit.

Also even if you subclass like this diagnosing the possible issues is not hard, and fixing it is easy as well. I think this is just a stupidly defensive programming, and the gains/losses are not in balance.

mikeash at 2009-11-27 20:57:00:
Those are all very good points, and I would tend to agree. I think I had these thoughts before, but they were not on the surface of my mind.

I think the ultimate question is this: does using accessors in init/dealloc increase or decrease coupling with the rest of your code? If it decreases it, then you should do it. If it decreases it, then you should not. And I think the answer to whether it increases or decreases coupling depends entirely on the situation at hand.

Kevin Ballard at 2009-11-27 23:08:52:
I used to use accessors in init/dealloc, knowing I was going against the recommendation from Apple, until one day it screwed up. It turns out that one of my coworkers wrote a version of -setFoo: that did not handle being given a nil object properly. So when I called self.foo = nil; in my -dealloc method, my app crashed. It was fairly trivial to fix this problem, but it did highlight for me one of the issues with using accessors in init/dealloc, which is that even if you're well aware of the caveats with doing so, your coworkers probably aren't.

mikeash at 2009-11-28 00:01:34:
I don't quite understand that one. Did it have some extremely subtle and difficult to find bug when passed nil? Normally, it'll throw an exception or crash, and the backtrace will pass through dealloc, and the nature of the error will be obvious. This sort of thing is not interesting to me. Code which leads to obvious errors is nearly as good as code which doesn't lead to errors at all. It is only code which leads to subtle, difficult to find/fix errors which I'm wary of.

Jens Ayton at 2009-11-28 00:29:14:
The recommendation against using accessors seems to have migrated out from Appleā€™s framework groups, where the need to be defensive against weird subclasses is greater than just about anywhere else.

mikeash at 2009-11-28 00:43:28:
Agreed. Which is ironic, because so many of Apple's classes do use accessors in their dealloc methods. (Of course, that could just mean that the recommendation comes from experience, and that they're unable to change this behavior because it would break compatibility.)

Jonathan Wight at 2009-11-28 08:34:34:
@Kevin.

So your co-worker wrote a accessor that had a bug in it - and you crashed. Calling setFoo: with nil could happen anywhere - not just in dealloc. Your co-worker shouldn't write buggy code.

I'm in the "I used to use accessors, continued to use them after the apple warnings, then finally caved in" camp.

Louis Gerbarg at 2009-11-28 17:43:44:
I used to always use accessors in init, and I agree it is generally safe unless the class is intended to be subclassed.

On the other hand, it royally bit me in the ass when I had a class that registered for KVOs after introspecting its property list, so that it could automatically do things with properties of subclasses. I think everyone can guess what happened there, it is basically the same problem that CoreData has internally, and they solved it with primative accessors.

The thing to understand is that (in CoreData paralance):

accessor == setValue:forObject:
no accessor == setPrimitave:valueForObject:

Unfortunately, primative accessors are not a concept builtin at the KVC/KVO level, I really wish they were. If you are using automatic KVO then the system could just build you @dynamic primative accessors for all your KVC compliant accessors.

Louis Gerbarg at 2009-11-28 18:09:11:
r/setPrimitave:valueForObject:/setPrimitiveValue:forObject:/

[self setNeedsCaffeine];

Sean M at 2009-11-29 17:20:36:
I also used to use accessors in init/dealloc but no longer do, figuring Apple knows better than I.

Also, with GC, there is no dealloc and rarely a need for finalize, so half the problem is solved there. For init, the coding error in "Pros of Accessors" isn't an error at all. Point being, using ivars directly in GC is not as error prone as with non-GC.

Vincent Gable at 2009-11-29 19:07:18:
One benefit to using properties instead of ivars in dealloc is that you can use the obj-c runtime to automatically discover and free them for you:
http://vgable.com/blog/2008/12/20/automatically-freeing-every-property/
That way you can't forget to release something, and it's less code.

But, I've gone to a [ivar release], ivar = nil; pattern after hearing Apple's recommendation. In my experience forgot-to-release-an-ivar bugs are relatively easy to track down and fix. But a side-effecting property being called in dealloc can be very nasty indeed!

mikeash at 2009-11-29 19:25:30:
I'm amused at the prevalent "Apple knows best" attitude. Bindings, garbage collection, NSOperationQueue, and so many other things, Apple has gotten wrong and burned me in the process. I always trust my own evaluation over their recommendations.

Jose Vazquez at 2009-11-29 21:03:56:
I just noticed that you used a double set of parentheses around the statement:

if ((self = [self init]))

But I can't figure out why. I went back to you post on initializers (http://mikeash.com/?page=pyblog/the-how-and-why-of-cocoa-initializers.html) and I see you did that there too, but I see no explanation why. I also tried looking up Apple's documentation, and they seem to use a single set of parentheses.

Is there any reason you use double parentheses?

Kevin at 2009-11-29 21:59:15:
I added support for this dealloc pattern [ivar release], ivar = nil; in Accessorizer 1.3. You can also turn accessors on or off in both init and dealloc. So for those of you who want this stuff done automatically for you, you might check it out.
http://www.kevincallahan.org/software/accessorizer.html

mikeash at 2009-11-29 22:52:54:
The vast majority of the time, when you write a statement like this:

if(a = b)

It's a mistake. You actually wanted ==, but forgot one of them.

Because this mistake is so common and so nasty, any reasonable compiler will catch it and warn about it. Gcc will do this if you compile with -Wall.

Of course, this is annoying when you actually want to assign in the if statement. The double parentheses just tell the compiler (and any humans reading the code) that you really did mean to assign, and not to compare.

Steven at 2010-03-16 05:21:22:
Good article. I have meen a long fan (>10 years) of using the accessors in the init and dealloc specifically because it tends to decrease code significantly. Oddly, I find people trying to use the subclass example of how that might break but it is equally easy to break the "Apple recommended" way in a subclass as well. For example, the subclass destroys an object (such as a timer), sets it to nil and the superclass goes to invalidate it but no longer has access to it. That is the specific reason I QUIT following Apple's advice on accessors.

In short, it is ALWAYS the subclasses responsibility to get the interaction to the superclass correct. I have found it is MUCH easier to do this by keeping Accessors simply that. Accessors. Likewise, doing code like:

 - (id)initWithWhatever: (id)whatever
    {
        self = [self init];
        [self setWhatever: whatever];

        return self;
    }

whenever I can (something you simply cannot do if you access the ivars directly) to really simplify the code substantially.

Add to this, the ease of memory management, and I see few reasons to NOT use the accessors at all times. Yes there are exceptions, but they are minor.

Malte Tancred at 2010-04-10 12:09:56:
Consider this:
- (void)dealloc {
  self.worker.delegate = nil;
  self.worker = nil;
  [super dealloc];
}


If this is from a subclass of NSThread, and you're non-GC, there might not be an autorelease pool in place to handle the retained and autoreleased 'worker' object. It doesn't matter if you use the new dot-notation or the normal brackets, i.e., [[self worker] setDelegate:nil], but the dot-notation makes it harder I think, to realize that you're actually using an accessor when resetting the worker's delegate.

mikeash at 2010-04-11 01:40:05:
...dot-notation makes it harder... to realize that you're actually using an accessor.


This is, incidentally, one of the major reasons why I never use dot notation.


Comments RSS feed for this page

Add your thoughts, post a comment:

Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.

Name:
Web site:
Comment:
Formatting: <i> <b> <blockquote> <code>. URLs are automatically hyperlinked.
Code syntax highlighting thanks to Pygments.
Hosted at DigitalOcean.