Objective-C init: Why It’s Helpful to Avoid Messages to self

I sometimes talk to myself. Our classes often do. But there are a couple of places where doing so is risky in Objective-C: init and dealloc.

"The Self-Talk Solution"

This post is part of the Code Smells in Objective-C series.

Here’s something I see a fair bit in code for Objective-C init and dealloc. I’ll give a simple example. Can you tell what’s wrong?

- (id)initWithFoo:(id)foo
{
    self = [super init];
    if (self)
        self.something = foo;
    return self;
}

- (void)dealloc
{
    self.something = nil;
    [super dealloc];
}

Hint: It’s those self-dots. They have a tendency to lull people into thinking they’re simple assignments. But remember, dot notation conceals messaging.

Let’s avoid dot notation and try again:

- (id)initWithFoo:(id)foo
{
    self = [super init];
    if (self)
        [self setSomething:foo];
    return self;
}

- (void)dealloc
{
    [self setSomething:nil];
    [super dealloc];
}

Now do you see it?

When messages to self are smelly

Sending messages to self is normally fine. But there are two places you want to avoid them:

  • When the object is being created, and
  • When the object is being destroyed.

At these two times, the object is in a funny, in-between state. It lacks integrity. Calling methods during these times is a code smell. Why? Because every method should maintain invariants as it operates on the object. Here’s the outline of an object’s self-consistency as it flows through a method:

  1. Start: Assume object is self-consistent.
  2. In-progress: Object state is in flux.
  3. End: Restore invariant that object is self-consistent.

#ProTip: Invariants will keep you sane.

I’m not striking out to unconventional territory for this. Apple has a document on Practical Memory Management that has a section titled, “Don’t Use Accessor Methods in Initializer Methods and dealloc.”

Objective-C init / dealloc: ivars to the rescue

The solution is simple: in Objective-C init and dealloc methods, access instance variables directly instead of going through properties. In non-ARC code, check your property attributes for “retain” or “assign”. Then code your direct access to match. For example, if something is a retain property with the default backing ivar _something, our code becomes:

- (id)initWithFoo:(id)foo
{
    self = [super init];
    if (self)
        _something = [foo retain];
    return self;
}

- (void)dealloc
{
    [_something release];
    [super dealloc];
}

When messaging self in init/dealloc can still work

Having said “avoid sending messages to self in init and dealloc,” I now want to soften that statement. There are two places when it might be OK after all:

  • At the very end of init, and
  • At the very beginning of dealloc.

That’s because in those two places, the object has self-consistency. In init, all the ivars have been set up. In dealloc, none of the ivars have yet been destroyed.

But you still have to exercise caution and recognize where you are in the object’s lifetime. Simply creating an object shouldn’t start any heavy-duty work. Keep creation and destruction light and quick.

What do you think? Share your thoughts and experiences in the comments below!

Did you find this useful? Subscribe today to get regular posts on clean iOS code.

About the Author Jon Reid

Jon is a coach and consultant on iOS Clean Code (Test Driven Development, unit testing, refactoring, design). He's been practicing TDD since 2001. You can learn more about his background, or see what services he can bring to your organization.

follow me on:

Leave a Comment:

42 comments
Add Your Reply