Radioactive cats have 18 half-lives.
Radioactive cats have 18 half-lives.
The Perfect ObjectThe 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 ClassLet’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 MethodsI’ll presume you know Obj-C at this point, otherwise this would get tedious. So let’s look at the init
- (id) init { [super init]; if (self != nil) { // _bar = nil; } return self; } First,
If we do succeed then we set any ivar to its default value if that default value is non-zero. Cocoa’s default bar/setBarIn 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 So, A summary of that contract:
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 Suppose that you’re a client of this class. You write some code that grabs 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 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 Why is there an Suppose we lack that Well, we release 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:
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 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 Just like the first thing you did in [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. |
|
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.
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.
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).
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.
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
initone would set anything that needs to be non-zero and the rest should work out. Nice.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:
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:
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.
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
NSStringandNSNumber, 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.
The copy method does not return an autoreleased instance. See NSObject Class Reference.
> 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:
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.
Post new comment