Minutia on Objective-C Categories and Extensions.

Posted by Matt Wilding on Stack Overflow See other posts from Stack Overflow or by Matt Wilding
Published on 2011-01-13T21:49:29Z Indexed on 2011/01/13 21:53 UTC
Read the original article Hit count: 230

Filed under:

I learned something new while trying to figure out why my readwrite property declared in a private Category wasn't generating a setter. It was because my Category was named:

// .m
@interface MyClass (private)
@property (readwrite, copy) NSArray* myProperty;
@end

Changing it to:

// .m
@interface MyClass ()
@property (readwrite, copy) NSArray* myProperty;
@end

and my setter is synthesized. I now know that Class Extension is not just another name for an anonymous Category. Leaving a Category unnamed causes it to morph into a different beast: one that now gives compile-time method implementation enforcement and allows you to add ivars. I now understand the general philosophies underlying each of these: Categories are generally used to add methods to any class at runtime, and Class Extensions are generally used to enforce private API implementation and add ivars. I accept this.

But there are trifles that confuse me. First, at a hight level: Why differentiate like this? These concepts seem like similar ideas that can't decide if they are the same, or different concepts. If they are the same, I would expect the exact same things to be possible using a Category with no name as is with a named Category (which they are not). If they are different, (which they are) I would expect a greater syntactical disparity between the two. It seems odd to say, "Oh, by the way, to implement a Class Extension, just write a Category, but leave out the name. It magically changes."

Second, on the topic of compile time enforcement: If you can't add properties in a named Category, why does doing so convince the compiler that you did just that? To clarify, I'll illustrate with my example. I can declare a readonly property in the header file:

// .h
@interface MyClass : NSObject
@property (readonly, copy) NSString* myString;
@end

Now, I want to head over to the implementation file and give myself private readwrite access to the property. If I do it correctly:

// .m
@interface MyClass ()
@property (readonly, copy) NSString* myString;
@end

I get a warning when I don't synthesize, and when I do, I can set the property and everything is peachy. But, frustratingly, if I happen to be slightly misguided about the difference between Category and Class Extension and I try:

// .m
@interface MyClass (private)
@property (readonly, copy) NSString* myString;
@end

The compiler is completely pacified into thinking that the property is readwrite. I get no warning, and not even the nice compile error "Object cannot be set - either readonly property or no setter found" upon setting myString that I would had I not declared the readwrite property in the Category. I just get the "Does not respond to selector" exception at runtime. If adding ivars and properties is not supported by (named) Categories, is it too much to ask that the compiler play by the same rules? Am I missing some grand design philosophy?

© Stack Overflow or respective owner

Related posts about objective-c