Accessor Methods, Part 2
Posted May 17th, 2003 @ 02:02pm by Erik J. Barzeski
I wrote about accessor methods in Cocoa before. One of the comments on that article implored me to check out Ali Ozer's talk at WWDC. I've done so, and I will now present what I've learned. I'm documenting this for myself, so if my explanation is brief, believe me when I say I understand what I've written. 😉
Properly written accessor methods allow you to write very straightforward code:
NSString *string = [myDoc title]; NSString *capString = [string capitalizedString]; [myDoc setTitle: capString];
The lines above ask for the document's title, capitalize that string, and set the capitalized version back as the new title. This could be done in one line:
[myDoc setTitle: [[myDoc title] capitalizedString]];
The "code just falls naturally." You get, you modify, you set. That's all there is to it. But how do we write the accessor methods to facilitate this ease? What is the "one true way" to write accessors (both setters and getters)? Some may think that this is okay:
- (void)setTitle:(NSString *)newTitle { title = newTitle; }
This of course will work fine if newTitle
is going to stick around. If not, we need to fix it:
- (void)setTitle:(NSString *)newTitle { title = [newTitle copy]; }
Though this fixes the problem with newTitle
, it also leaks the old title
. Leaks are bad. Let's fix that then:
- (void)setTitle:(NSString *)newTitle { [title release]; title = [newTitle copy]; }
This might crash if title
and newTitle
are the same. So we take the next logical step:
- (void)setTitle:(NSString *)newTitle { if(title != newTitle) { [title release]; title = [newTitle copy]; } }
That works. It fixes all of the problems we've encountered and it's a little more efficient. Here's another:
- (void)setTitle: (NSString *)newTitle { [title autorelease]; title = [newTitle copy]; }
This also works. We get rid of the original title
"later." This works even if the two titles are the same. So both work, and every time both work, you've got to ask yourself a question: which is better? The one with release
or autorelease
? The answer depends on what the "get" method looks like and its usage pattern.
- (NSString *)title { return title; }
Is this one very "safe"? Look at this code:
NSString *string = [myDoc title]; [myDoc save]; [myLogFile recordAsSaved:string];
This illustrates why the getter and the setter have to be considered in tandem. In the code just above, the save function may change the title, releasing the old one in the process. Passing a released string to recordAsSaved:
can't have good results. These are a bad pair:
- (NSString *)title { return title; }
- (void)setTitle:(NSString *)newTitle { if(title != newTitle) { [title release]; title = [newTitle copy]; } }
However, this pair is just fine:
// Style 1 - (NSString *)title { return [[title retain] autorelease]; }
- (void)setTitle:(NSString *)newTitle { if(title != newTitle) { [title release]; title = [newTitle copy]; } }
Of course, there's still the other way to do this using the alternate set method:
// Style 2 - (NSString *)title { return title; }
- (void)setTitle:(NSString *)newTitle { [title autorelease]; title = [newTitle copy]; }
Effectively, the autorelease in the set method extends the lifetime of the object. So again we're down to the question "which is better?" We're smart, so we might also ask "why?"
The second pair is best if the performance of the "get" method is important. As you can see, it's just a return
.
Typically, the first one is most appropriate. When you're dealing with threads, the first is safer because the returned object measures its lifetime in the thread in which it was called. Thus, the object hangs around nicely even if another thread has set a new value (releasing the old one, rendering your first thread's pointer useless).
This leaves us with one final question: copy
or retain
?
- (void)setTitle:(NSString *)newTitle { [title autorelease]; title = [newTitle retain]; // or copy ? }
copy
makes a brand new copy of the object while retain
increments the retain count. The answer depends on whether you're interested in the actual value of the object or the object itself.
- (void)setTitle:(NSString *)newTitle { [title autorelease]; title = [newTitle retain]; }
str = [NSMutableString string]; // 1. Empty string [str appendString: @"Hello"]; // 2. Modify str [doc1 setTitle: str]; // 3. Set it as the title [str appendString: @"World"]; // 4. Modify str further [doc2 setTitle: str]; // 5. Set it as the title
In line four, suddenly doc1
's title has changed. Thus, you'd want to use copy
and not retain
in this case. The document "owns" the title. It can set a new one, it can loan it to others, but it's title is "its own." Conversely, a number of examples exist which don't imply ownership:
- NSView's superview
- NSControl's target
- NSTableView's data source
- Delegates
- etc.
In each of these cases, it's okay to hold on to an object without retaining it. However, care must be taken when releasing the object. Think of it this way: when you release a view, you don't release the parent view. An example: if you give an object a delegate, and you release the delegate, you should tell the object to set its delegate to nil so it doesn't try to talk to it anymore.
So there we have it. We've learned about 256 combinations of setters and getters, about eight of which are appropriate some of the time, and about 72 of which are appropriate sometimes. I hope that clears it all up! Heh heh. 😀
Posted 17 May 2003 at 2:48pm #
Nice! An excellent summary, and very informative--thanks!
Posted 17 May 2003 at 4:51pm #
Nice one.
Reminds me of why I've been suspicious about Cocoa/Objective-C: You have to worry about memory stuff.
Sometimes I actually started to like that as it forces me to be more clear about object ownership etc. and to not simply rely on things 'going away' at some stage.
On the other hand, it does add an extra layer of complexity which I find quite absurd in the
[[object retain] autorelease]
. Not very æsthetic. If you needn't worry about memory that much you could probably focus more on whether you want to set/retrieve the object or merely its data (i.e.copy
it). That is an important conceptual distinction, rather than a technical one: Do I need the data here or the correct reference?As you point out, frequently you'll want a reference to the object, such as the superview or the delegate, in other cases you'll actually want the data and you'll want a copy of it that cannot be modified by anyone else. Thus I suggest adding
- (NSString *)title
{
return [[title copy] autorelease];
}
to your zoo of accessor methods, for the case where you want to return the value rather than the object. I read somewhere the - very plausible - recommendation to use this wherever you are not 100% sure about the future usage of your class to prevent that kind screwing things up you illustrate in the five step example.
I think the difference between sharing the actual object or only passing on its data isn't made clear in many texts. And I find that it's another place where it can be helpful to sit down and ask yourself "What do I want to set/retrieve?" and be more clear about what your program is doing.
Of course as a corollary of this, you'll end up being very keen to implement
NSCopying
for your classes, and all those other nifty things 🙂What do you think about
NSNumber
s, btw? I'm always disappointed that you can't do computations with them and have to use those old-fashionedint
s anddouble
s for that purpose, where you'd want to use NSNumbers for everything else - meaning that you'll end up converting things back and forth which is ugly.... and of course you'll have less lines of code starting with a bracket. I don't like those. They look like you're writing your code in C. Eeeek.
Posted 17 May 2003 at 8:47pm #
On the topic of accessors: should you use your class' accessors when you're writing other methods in that class or is it alright to call the member variables directly?
Posted 17 May 2003 at 8:59pm #
The general rule of thumb is to use your object's accessor methods, particularly in the set methods. After all, your set methods may do some sanity checking of their own (
!= nil
for example) that you don't want to circumvent. Besides, you'd have to inline your set method anyway (or you'd leak or something). Customized "gets" aren't terribly abundant, but they can happen, and it's better to know later down the line that everything is going through your accessors than to track down the ones that aren't and add some logic/sanity-checks to those as well.Very rarely would I recommend accessing instance variables directly.
Posted 17 May 2003 at 9:20pm #
Even then you are better off using the accessor. If the accessor only does return foo; you can save yourself the method dispatch, but once that accessor changes to become more complicated you have to find everywhere you accessed foo and go through the accessor. If the accessor does something more complicated than return foo; then not using the accessor means you have to replicate that logic all over the place.
Posted 18 May 2003 at 5:49pm #
Jamie's written in a far better manner than I could an entry discussing the self-censorship one must apply to their blogging. She also links to...
Posted 18 May 2003 at 6:49pm #
You should see if you can get a condensed/simplifed version of this onto CocoaDevCentral. Maybe the same basic idea without the surrounding discussion.
- Scott
Posted 21 Jun 2003 at 1:54pm #
Scott wrote:
"You should see if you can get a condensed/simplifed version of this onto CocoaDevCentral."
There's been a "more condensed version" at http://www.stepwise.com/Articles/Technical/2002-06-11.01.html for some time...
mmalc
Posted 21 Jun 2003 at 6:43pm #
I'd hardly call that more condensed.