1

I'm trying to add an instance variable and a property to an existing class. I want to do this to extend the base class of an open source library, without modifying the source code (for easier code management).

The documentation says

Unlike regular categories, a class extension can add its own properties and instance variables to a class. If you declare a property in a class extension [...] the compiler will automatically synthesize the relevant accessor methods, as well as an instance variable, inside the primary class implementation.

I tried to do this in a test app. I have an empty class ClassA:

ClassA.h

#import <Foundation/Foundation.h>
@interface ClassA : NSObject
@end

ClassA.m

#import "ClassA.h"    
@implementation ClassA
@end


Then I added an extension:

ClassA+Ext.h

#import "ClassA.h"
@interface ClassA ()

@property (nonatomic) NSString *name;      // <-- this is my new property

@end


Finally in my AppDelegate I simply init a ClassA and try to set and then log the name.

#import "AppDelegate.h"
#import "ClassA.h"
#import "ClassA+Ext.h"

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    ClassA *a = [[ClassA alloc] init];
    a.name = @"test";
    NSLog(@"Name: %@", a.name);
}

@end

This compiles and runs, but I get this error on the console:

2013-05-10 00:58:08.598 Test[53161:303] -[ClassA setName:]: unrecognized selector
sent to instance 0x108a1a630
2013-05-10 00:58:08.600 Test[53161:303] -[ClassA setName:]: unrecognized selector 
sent to instance 0x108a1a630

The name is not logged. And if I set a breakpoint and inspect the ClassA instance, it doesn't have a name ivar. What am I doing wrong? Isn't this actually possible to do?

DrummerB
  • 39,814
  • 12
  • 105
  • 142
  • The error has nothing to do with an ivar being there or not. The problem is that the property's setter method isn't recognized. Comment out the line `a.name = ...` and see if you have any issue with the "getter" in the log statement. – rmaddy May 09 '13 at 23:04
  • If I do that, I get the error for the getter `[ClassA name]` – DrummerB May 09 '13 at 23:07
  • 1
    As the deleted answer states, properties added in a class extension are private to the class. This is why another class can't see the setter or getter for the private property `name`. – rmaddy May 09 '13 at 23:11

2 Answers2

3

Your understanding is incorrect. You added a property through a class extension. This property should only be able to be used inside your class. It should not be used as a true "property" for your class in which you can change it through external instantiation. Think of it as a pseudo private instance variable.

I believe what you want to do is simply subclass your Class A.

Another viable solution proposed was:

"You can write a category to wrap it into -[setAssociatedObject:forKey:]" when using objc_setAssociatedObject --> by @Artur

Ben Coffman
  • 1,750
  • 1
  • 18
  • 26
  • I can't subclass, because this is the base class of a library and other classes in the library subclass it already. I would have to change a lot to insert an additional class in the hierarchy, making the code very cumbersome to maintain. – DrummerB May 09 '13 at 23:15
  • Then subclass the class that needs the variable in it, you don't have to do it at the root. – Ben Coffman May 09 '13 at 23:16
  • Unfortunately all of them need it. It's like the creator of the library forgot to add the usual `userData` or `userInfo` ivar to the API. – DrummerB May 09 '13 at 23:18
  • If all of them need it then the only way to fix this is to subclass the root. Even if your example worked above as you intended the circumstance you are stating in the comments above would not be any different. – Ben Coffman May 09 '13 at 23:21
  • 1
    @DrummerB use objc_setAssociatedObject & co. –  May 09 '13 at 23:22
  • If you really want access to it you can hack it by using "->" to access and set it, but no promises on the outcome. – Ben Coffman May 09 '13 at 23:23
  • I tried, it yields a compile error, because I'm accessing a private ivar. – DrummerB May 09 '13 at 23:24
  • @Artur Looks like my last resort. Thanks – DrummerB May 09 '13 at 23:25
  • @DrummerB Please read about objc_setAssociatedObject. It is what you are looking for. –  May 09 '13 at 23:25
  • @DrummerB It is not "resort". It is (c-style) userInfo for each object. You can write a category to wrap it into `-[setAssociatedObject:forKey:]` or something. –  May 09 '13 at 23:26
  • @Artur I'm just concerned about the performance a little bit, since it isn't a direct access, but via keys. I'll do some tests tomorrow. Thanks for the hint. – DrummerB May 09 '13 at 23:29
  • @DrummerB Hashing on pointer is just division of its integral value by some constant (and one additional integer comparison on collision). Penalty must be low. –  May 09 '13 at 23:38
  • Also all class methods and properties in Objective c are accessed via a "key" at runtime. – Ben Coffman May 09 '13 at 23:40
  • @Benny They promised to optimize such accesses, but for first time in scope -- yes, it is just regular lookup. Good point is DO NOT OPTIMIZE ANYTHING in Objective-C unless you are really slow. –  May 09 '13 at 23:50
  • Looks like this will be the solution. Thanks, Artur, Benny. – DrummerB May 10 '13 at 00:19
  • 1
    @Benny I think it is better to move refined question and it's solution into your answer, than leaving all this in comments. –  May 10 '13 at 00:32
0

I think class extensions are meant to be put in the class.m files, usually above the @implementation of the class.

robbartoszewski
  • 896
  • 6
  • 11