My Kind of Stupid

Web development, user interfaces, and other development topics.

Implementing a Mutable Subclass in Objective-C

I'm writing a class in Git Push to hold and fetch git blobs from Github.

@interface GHBlob : NSObject <NSCopying, NSMutableCopying>
@property (nonatomic, copy, readonly) NSData *content;
...
@end

@implementation GHBlob
@synthesize content = _content;
...
@end

To support creating and updating blobs, I wrote a mutable subclass initialized in #mutableCopy from NSMutableCopying. This entailed redefining properties as readwrite:

@interface GHMutableBlob : GHBlob
@property (nonatomic, copy, readwrite) NSData *content;
...
@end

@implementation GHMutableBlob
@synthesize content = _content;
...
@end

Problems

Resynthesizing

As shown above, I also re-synthesized the methods in the subclass. However, this resulted in the subclass's initializer breaking.

GHBlob *newBlob = [blob mutableCopy];
STAssertEquals(newBlob.content, blob.content, nil);
  //=> '<00000000>' should be equal to '<10cbb806>'

On review, I noticed something I'd missed before in Apple's documentation on redeclaring properties. The redeclared property isn't synthesized in their example, it's declared @dynamic and the setter is defined by hand. I scoffed at the inconvenience (isn't this what synthesizing is supposed to avoid?), but did it anyway.

Unreachable instance variables

Next, the compiler complained that the _content instance variable was unreachable. Oh, so this is why we define ivars in the interface - it makes them available to subclasses.

Summary

So to implement NSMutableCopying, I had to explicitly declare my instance variables in the interface and explicitly define setter methods in my mutable subclass. I thought the property/synthesize syntax was saving me the trouble, but apparently it's not all rainbows and unicorns.

@interface GHBlob : NSObject <NSCopying, NSMutableCopying> {
  NSData *_content;
}
@property (nonatomic, copy, readonly) NSData *content;
...
@end

@implementation GHBlob
@synthesize content = _content;
@end

And in the mutable subclass:

@interface GHMutableBlob : GHBlob
@property (nonatomic, copy, readwrite) NSData *content;
...
@end

@implementation GHMutableBlob
@dynamic content;
- (void)setContent:(NSData *)content {
  _content = [content copy];
}

Published at