Hopeless Geek

Tagline

Radioactive cats have 18 half-lives.

Home » Blogs » Adam Knight's blog

The Perfect Object


  • Cocoa
October 18, 2007 - 4:58pm

The problem I had when I was starting out with Cocoa so many years ago was finding the answer to “Why?” rather than “How?” questions. There’s a lot of code out there these days and a lot of people writing code on educated guessed gleaned from reading that code.

So I’m going to make a long read a short one and explain the perfect Cocoa object. Once you have this down, memory management just comes along for free, as does KVC/KVO and mnemonic code.

The Foo Class

Let’s start off with the end result, shall we?

Foo.h

//

//  Foo.h

//  

//  Created by Adam Knight on 10/18/07.

//  Copyright 2007 Adam Knight. All rights reserved.

//


#import <Cocoa/Cocoa.h>


@interface Foo : NSObject {

NSString *_bar;

}


- (NSString*) bar;

- (void) setBar: (NSString*) newValue;


@end


Foo.m

//

//  Foo.m

//  

//  Created by Adam Knight on 10/18/07.

//  Copyright 2007 Adam Knight. All rights reserved.

//


#import "Foo.h"



@implementation Foo


- (id) init

{

[super init];

if (self != nil) {

// _bar = nil;

}

return self;

}


- (void) dealloc

{

[self setBar: nil];

[super dealloc];

}


- (NSString*) bar

{

return [[_bar retain] autorelease];

}


- (void) setBar: (NSString*) newValue

{

if (newValue != _bar) {

// [self willChangeValueForKey:@"bar"];

[_bar release];

_bar = [newValue retain];

// [self didChangeValueForKey:@"bar"];

}

}


@end

Methods

I’ll presume you know Obj-C at this point, otherwise this would get tedious. So let’s look at the init method and see what’s going on there.

init

- (id) init

{

[super init];

if (self != nil) {

// _bar = nil;

}

return self;

}

First, init is expected to return an object that’s ready to go or return nil if there was a problem. If super’s init returned nil then it would also set self to nil as well. So, we call super’s init and then check self for nil to see if there was an error. Either way, we return the success of super’s init since we have no real way of failing at this point.

If we do succeed, then we give every instance variable (“ivar”) a value so that when we go to access them we don’t crash. Crashing’s bad.

If we do succeed then we set any ivar to its default value if that default value is non-zero. Cocoa’s default alloc method pre-sets all ivars to a zero value (which means nil for objects, consequently) so you don’t have to worry about uninitialized ivars. (Thanks Jeff.)

bar/setBar

In Key-Value Coding you must set your getters and setters (aka accessors and mutators) to property and setProperty. Case matters. So here we have an ivar of _bar. It has an underbar in front so that some enterprising subclasser doesn’t try and pull a object->bar on us and start messing around with things behind the scenes. The underbar is a “Leave me alone.” sign.

So, bar is an object (an NSString, to be precise). Objects come with a memory management contract. It’s not that hard, really. Here, we’re creating and accepting, so we’re getting both ends of the memory management contract.

A summary of that contract:

  1. If you changed an object’s retain count, you are responsible for putting it back when you’re done. (Also known as: Anything you create with alloc, new, copy, or keep with retain must have a matching release or autorelease in your code when you’re done with it.)
  2. If you did not increase an object’s retain count, don’t be concerned with it. Leave it alone.
  3. Any result of a method must be valid at least until the end of the event loop.
  4. If you want something to last beyond the end of the event loop, it’s up to you to call retain and keep it somewhere and then release it when you’re done with it.

That’s my summary and interpretation, at least. Apple has the official rules posted.

bar

- (NSString*) bar

{

return [[_bar retain] autorelease];

}

This is fairly simple to look at, but has one feature that’s not quite obvious. Really, it’s just returning the ivar of a different name. However, notice that it has a retain and autorelease wrapper around it. Why would it have that? Rule #3.

Suppose that you’re a client of this class. You write some code that grabs bar and then starts through a long method with the value you got back. At some point, you set bar to something else. The setBar method will release the ivar and then set the ivar to something else. Anyone that has a reference to the original bar value will be left with a dead pointer because release is instant (if the retain count is zero, which is the worst-case scenario we’re considering here).

This will be a pain in the ass to track down because code elsewhere will crash, not this code. So, to solve that you have to fulfill the memory contract and ensure that anything you give back to the world is valid for the remainder of the current event loop. Since the autorelease pool is flushed at the end of the event loop, you up the retain count by one with a retain and then mark it with autorelease to have that removed at the end of the event loop. Any code that retrieves it will be able to hold onto it for the current event loop — longer still if it retains it and keeps it somewhere.

setBar

- (void) setBar: (NSString*) newValue

{

if (newValue != _bar) {

// [self willChangeValueForKey:@"bar"];

[_bar release];

_bar = [newValue retain];

// [self didChangeValueForKey:@"bar"];

}

}

As per the memory contract, if we want to hold on to something then we have to retain or copy it and stuff it somewhere. So in this setter we release the old value and set the ivar to the new value and retain it. Simple.

Why is there an if statement in there? At first glance, it’s a needless optimization that says “if these are actually the same object, don’t bother.” Ehh, close. If we’re replacing an ivar with the same object, it’s actually a little tricky.

Suppose we lack that if statement and we have a string with a retain count of 1 in the ivar _bar. Suppose also that we are told to set bar to the same value, as with: [barObj setBar:[barObj bar]]. Or, worse, we use the following inside the class: [barObj setBar:_bar]. What happens?

Well, we release _bar. If the getter is not honoring the memory contract then that invalidates newValue and setting _bar to newValue will result in a crash. Whoops.

Now, if our getter is following the memory contract then that’s not a problem; it’ll be around for the rest of the event loop. However, there are many ways of making a getter and depending on the one you choose, you may not be able to rely on that behavior. Some other getter ideas:

  • return _bar;
  • return [[_bar copy] autorelease];
  • return [[_bar retain] autorelease];

That first one would break this mightily. So, we check for equality before moving on. It’s not always strictly required, but it’s defensive coding and you should get in the habit of doing it. All objects obtained from other methods or classes are considered volatile and should be treated with care if you start to change their retain count.

One last feature of this method is the KVO pair will/didChangeValueforKey. This tells any observers that this object is changing and lets them do anything as a result. Always use this. Always use this in Core Data projects, for it doesn’t do the Right Thing™. However, in standard projects, Apple may be taking care of you here. (Thanks Brian.)

Either way, always make sure this works in your project. You may want to observe your code in the future, or you may want to use bindings (which relies on this heavily), or other code may want to attach to methods on your code that return an object and follow that. If you return a useful object whose getter is speedy, then notify observers when it’s changed.

dealloc

- (void) dealloc

{

[self setBar: nil];

[super dealloc];

}

Along the same lines as init, we have to make sure that everything is cleaned up when we leave, so any ivar that could possibly have a retained object must be cleared. Some people choose to manually send release to each ivar and then set it to nil. Frankly, that’s wasteful. We already have setter and getter methods that will do this for us, so use them. Not only does it save you code, but it makes a lot more sense visually.

Just like the first thing you did in init was to call super’s init, the last thing you’ll do in dealloc is call super’s dealloc. You must not call it before you’re done. It’ll kick the floor out from underneath you. Clean up after yourself and then tell super to do the same.

[entry autorelease];

Well, I hope it was useful. If there’s anything wrong with the above, and I’m sure that someone has a different way of thinking, feel free to discuss it below.

Average: 5 (1 vote)
  • Adam Knight's blog
  • Printer-friendly version
October 19, 2007 - 7:45am
Brian Webster said

The KVO notification stuff is actually handled automagically by Cocoa using a bunch of behind the scenes runtime hackery, so adding your own willChangeValueForKey: and didChangeValueForKey: is redundant. If you only want to send a notification when newValue != _bar, you can override +automaticallyNotifiesObserversForKey: to return NO for the key “bar” (and make sure to call through to super for all other keys), and then call those KVO methods manually inside the setter.

  • reply
October 19, 2007 - 11:20am
Adam Knight said

Aha, so it is. After I did a couple of tests I discovered that if you do it the way I had it listed that you’d get notified twice when things happened, which could cause all sorts of bugs if you’re waiting on an event to happen.

However, it is still something that must be manually done in Core Data applications which are primarily what I work on these days, so the “easy” part of Cocoa was lost on me. Smiling

Of course, it would also be required any time you set the ivar outside of the setter/getter pair (but you have a setter/getter, so you should use them and get around it).

  • reply
October 19, 2007 - 8:26am
Jeff Johnson said

The alloc method will initialize all ivars to 0, so setting _bar = nil in init is redundant. It won't crash. See NSObject Class Reference.

  • reply
October 19, 2007 - 11:24am
Adam Knight said

I’d honestly never noticed that. Good catch, there. I ran a couple of tests on the test object above and it appears to work fine without it.

So then in init one would set anything that needs to be non-zero and the rest should work out. Nice.

  • reply
October 23, 2007 - 4:33pm
Jonathan Lehr said

Hm, it's been a long time (sadly) since I coded in Objective C, but I had a thought about your implementation of the accessor for the ivar _bar:

  1. - (NSString*) bar
  2. {
  3. &nbsp;&nbsp;&nbsp;&nbsp;return [[_bar retain] autorelease];
  4. }

While this is technically correct, it's far more common to implement an accessor that return a copy of the ivar rather than the ivar itself, as in:

  1. - (NSString*) bar
  2. {
  3. &nbsp;&nbsp;&nbsp;&nbsp;return [bar copy];
  4. }

Sharing the underlying ivar violates encapsulation to a certain degree, because it allows an object's state to be changed by an external object. While this is sometimes useful enough to justify the additional risk (IMO), most of the time (some would argue, all of the time, but that's not my flavor of Kool-Aid) it's better to return a copy.

Since copy methods return an autoreleased instance, your code doesn't have to do anything more than return the object returned by the call to copy.

  • reply
October 23, 2007 - 5:00pm
Adam Knight said

It’s a possibility if it’s a mutable object, but most objects in Cocoa are not mutable, and some are not copyable. The most common objects to get back are NSString and NSNumber, neither of which are mutable. To make any changes to the values of either object you would have to make a mutable copy of it in the first place, which is then a copy and makes the issue moot.

Of course, that’s a good point to cover for Cocoa newbies: Why are the default classes segregated into immutable and mutable classes? This is one reason. It’s faster to send one instance of a value around the block than it is to keep copying it for fear of a client of your class doing something weird with it (as in some other languages).

Also, copying objects every time they are accessed can (in theory) cause a slowdown of the program if you’re feeding something like a large table or data processing loop with the data. Every time the data is used it has to make a copy of it versus passing along a pointer to an immutable value.

  • reply
October 23, 2007 - 9:38pm
Jeff Johnson said

The copy method does not return an autoreleased instance. See NSObject Class Reference.

  • reply
October 25, 2007 - 7:06am
Jonathan Lehr said

> The copy method does not return an autoreleased instance.

Doh! That’s what I get for programming in Java for too long. Of course Jeff’s right, copy, copyWithZone:, alloc, and allocWithZone: are the methods (IIRC) that are NOT supposed to return an autoreleased instance, and the accessor method should call autorelease on the copied object:

  1. - (NSString*) bar

  2. {

  3.     return [[bar copy] autorelease];

  4. }


And Adam’s point is well taken — copying immutable objects would be superfluous and inefficient in most cases, though not all objects have immutable base classes. To take that one step further, developers often may not think of writing immutable classes with mutable subclasses when designing their own custom objects, but it’s a very useful (and easy to implement) pattern, especially for objects that are going to be passed around frequently.

  • reply

Post new comment

The content of this field is kept private and will not be shown publicly.
 
Input format
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. Beside the tag style "<foo>" it is also possible to use "[foo]".
  • Link to Amazon products with: [amazon product_id inline|full|thumbnail]. Example: [amazon 1590597559 thumbnail]
  • You can use Textile markup to format text.
  • Textual smileys will be replaced with graphical ones.
  • You may insert videos with [video:URL]
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. Beside the tag style "<foo>" it is also possible to use "[foo]".
  • Link to Amazon products with: [amazon product_id inline|full|thumbnail]. Example: [amazon 1590597559 thumbnail]
  • You can use Markdown syntax to format and style the text. Also see Markdown Extra for tables, footnotes, and more.
  • Textual smileys will be replaced with graphical ones.
  • You may insert videos with [video:URL]
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. Beside the tag style "<foo>" it is also possible to use "[foo]".
  • Link to Amazon products with: [amazon product_id inline|full|thumbnail]. Example: [amazon 1590597559 thumbnail]
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Textual smileys will be replaced with graphical ones.
  • You may insert videos with [video:URL]

More information about formatting options

Syndicate content Syndicate content

Site Navigation

  • Home
  • Recent
  • Popular
    • Today
  • Top rated
    • Recent votes
  • Elsewhere
    • FriendFeed
    • Friends
    • Software
    • Unsane
View Adam Knight's profile on LinkedIn

Navigation

  • My votes

Recent comments

  • Do you have any idea as to
    4 days 8 hours ago
  • Absolutely amazing when you
    5 days 21 hours ago
  • I am pro-choice, but not for
    2 weeks 1 day ago
  • My apologies. It is your
    2 weeks 2 days ago
  • Well, first, get your own
    2 weeks 2 days ago
  • There is nothing mythical
    2 weeks 2 days ago
  • Well, the number of square
    2 weeks 6 days ago
  • I think you’re wrong by a
    2 weeks 6 days ago
  • I couldn’t agree more! I am
    2 weeks 6 days ago
  • I think those numbers are
    3 weeks 21 hours ago

Today's popular content

  • Careful, America... (159)
  • Do-It-Yourself Smart Radio Station (10)
  • Krispy Kreme bacon cheddar cheeseburgers (8)
  • Panther's Major Text Services Upgrade (6)
  • The Obama Movie (6)
more

Hopeless Geek Feeds

  • Hopeless Geek
  • Hopeless Geek - Comments

Quotes

“These are the days when the Christian is expected to praise every creed except his own.” — ILN 8-11-28 – G. K. Chesterton

Footer Links

  • Badges
  • Contact
Powered by Drupal, an open source content management system
© Adam Knight, All Rights Reserved except where otherwise noted.