iOS Recipes - Matt Drance [71]
ClassExtension/PRPBook.h
@interface PRPBook : NSObject {
@private
NSString *title;
NSArray *authors;
BOOL inStock;
}
@property (nonatomic, copy, readonly) NSString *title;
@property (nonatomic, copy, readonly) NSArray *authors;
@property (nonatomic, assign, readonly, getter=isInStock) BOOL inStock;
@end
It’s a shame that those private instance variables (ivars) are visible in the header for everyone to see. It’s also redundant to have the properties and the ivars listed. Well, as of the iOS 4.0 SDK, we can eliminate this redundancy and just declare the properties: the tools and runtime synthesize the underlying ivar for us. This means that we can type less, have less to read, and, more importantly, not have private data in a public header.
Runtime Trivia
The iOS device runtime has always been able to synthesize ivars; it’s the iPhone Simulator runtime that required explicitly declared ivars. With the 4.0 SDK, the simulator has been brought up to speed, and we can omit ivar declarations where a property exists.
Our new header file reads much better without the redundant ivar declarations, protects our ivars from external access, and gives our poor hands and wrists a break.
ClassExtension/PRPModernBook.h
@interface PRPModernBook : NSObject {}
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSArray *authors;
@property (nonatomic, assign, readonly, getter=isInStock) BOOL inStock;
@end
Now let’s look at the implementation. We of course synthesize our properties, and we also have an internal method for refreshing the book’s in-stock or out-of-stock status. The calling code should never have to worry about refreshing the object, so we want to leave this method out of the header.
ClassExtension/PRPBook.m
- (id)init {
if ((self = [super init])) {
[self refreshStock];
}
return self;
}
- (void)refreshStock {
// ...
}
When we build this code, the compiler throws a warning because ‑refreshStock is referenced in ‑init but defined below it. The compiler reads from the top down, so we can fix this problem either by declaring ‑refreshStock in the header or by moving it up above ‑init. Neither option is desirable, however, because we want it to be private—not displayed in the header. And we don’t want the compiler dictating the structure of our code. So, how do we make both ourselves and the compiler happy?
The answer lies in private class extensions, a new feature of Objective-C 2.0. Class extensions are effectively private interfaces to the class you’re writing, typically defined alongside your standard implementation. These extensions look very much like categories, but they are significantly different.
You define a private class extension by declaring an interface with the same name as your class, followed by empty parentheses. You then fill that interface with any methods you don’t want in the header but need to formally declare for correctness.
@interface PRPModernBook ()
- (void)refreshStock;
@end
Again, this looks just like a category. But the big difference here is that this extension is treated as a formal component of the class. So, the compiler actually complains if it does not find matching implementations. (That’s not so for categories.) This policy protects you at compile time from making potentially harmful omissions or typos.
The other big difference is that you can declare new storage (synthesized properties) in a class extension. Categories, by contrast, only support the addition of functionality (methods). So if we have a property that we want to use internally, and not expose in the header, we can now do that with a class extension. Combined with synthesized ivars, the private data is completely absent from the header.
@interface PRPModernBook ()
@property (nonatomic, retain) id privateContext;
- (void)refreshStock;
@end
It gets better. The inStock property is naturally defined in the header as read-only, since we don’t want an arbitrary caller modifying that state. But wouldn