25

I'm trying to declare properties that are for internal use only in a Private category as such:

@interface BarLayer (Private)

@property (readwrite, retain) MenuItemFont  *menuButton;
@property (readwrite, retain) Menu          *menuMenu;
@property (readwrite, retain) LabelAtlas    *messageLabel;

@end

Now I'm trying to figure out where exactly I'm supposed to @synthesize those.

I tried:

@implementation BarLayer (Private)

@synthesize menuButton      = _menuButton;
@synthesize menuMenu        = _menuMenu;
@synthesize messageLabel    = _messageLabel;

@end

Here, the compiler complains:

@synthesize not allowed in a category's implementation

So I tried putting it in my BarLayer implementation, but here it doesn't find the declarations in the BarLayer interface.

no declaration of property ‘menuButton’ found in the interface

What would the correct way be?

lhunath
  • 120,288
  • 16
  • 68
  • 77
  • I think you should change the correct answer to this question, please check out my answer: http://stackoverflow.com/a/7400441/662605 regards – Daniel Jan 12 '12 at 10:24

6 Answers6

44

You can't use @synthesize with a category.

You can do this with an class extension (a.k.a. anonymous category), which is just a category without a name whose methods must be implemented in the main @implementation block for that class. For your code, just change "(Private)" to "()" and use @synthesize in the main @implementation block along with the rest of your code for that class.

See the Apple docs on extensions for more about that. (Apparently this is new in Mac OS 10.5.)

EDIT: An example:

// Main interface (in .h)
@interface Foo : NSObject
- (void)bar;
@end

// Private interface (in .m, or private .h)
@interface Foo ()
@property (nonatomic, copy) NSString *myData;
@end

@implementation Foo
@synthesize myData; // only needed for Xcode 4.3 and earlier
- (void)bar { ... }
@end

Another solution, which is much more work, is to use objc_setAssociatedObject and objc_getAssociatedObject to fake additional instance variables. In this case, you could declare these as properties, and implement the setters and getters yourself using the objc_* runtime methods above. For more detail on those functions, see the Apple docs on Objective-C runtime.

Jesse Rusak
  • 56,530
  • 12
  • 101
  • 102
  • 1
    +1 for the extension way. I also wrote a quick comparison of the different types of category in this answer http://stackoverflow.com/questions/360968/category-usage-in-objective-c/361140#361140 – Abizern Apr 13 '09 at 11:59
  • @Abizern - nice reference. You could also link that answer to this question for more info about properties. – Jesse Rusak Apr 13 '09 at 12:02
  • Worth noting: apparently the URL for the docs on extensions has changed to: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html – David Cairns Jul 18 '11 at 19:45
  • This is great but does it really answer the question? I need to also use synthesize in my category, and ran into this post. I needed to use some properties in my Category, I needed to use the dynamic directive and implement setIVar: and IVar for accessors. – Daniel Sep 13 '11 at 10:25
  • @Daniel: that's true; if you don't actually need storage for those properties (i.e. you already have a place to store them) then just implementing just the getters/setters works, too. You don't want to use ```@dynamic``` in this case; just don't say ```@synthesize```. – Jesse Rusak Sep 13 '11 at 13:36
  • Need clarification I have added category on UILabel class and added a property in category and implemented setter and getter. which work fine where as using @synthesis is not working as it's does the same thing behind the scene. ? – Sanoj Kashyap Jun 22 '15 at 11:57
13

I found an explanation of why synthesis of properties is prohibited in categories, but how you can use class extensions instead:

The following information comes from http://www.friday.com/bbum/2009/09/11/class-extensions-explained/

"The reason synthesis was prohibited in categories was because synthesis requires storage and there was no way of declaring storage in a category efficiently and it wasn’t deemed acceptable to allow a category to synthesize methods that would then diddle the class’s ivars directly. Too fragile and ugly.

However, it was also obviously desirable to be able to declare a property that was publicly readonly, but whose implementation was readwrite for internal-to-the-class-or-framework purposes.

One additional requirement is that the synthesis such properties must always be able to synthesize both the setter and getter naturally and precisely. Specifically, when declaring a property as atomic, there is no way the developer can correctly manually write only 1/2 of the getter setter pair; the locking infrastructure is not exposed and, thus, there is no way to guarantee atomicity in such a situation.

Class extensions addressed this problem elegantly.

Specifically, you can declare a property like:

@interface MyClass : NSObject
@property(readonly) NSView *targetView;
@end

And, then, in the implementation file:

@interface MyClass()
@property(readwrite) NSView *targetView;
@end

@implementation MyClass
@synthesize targetView;
@end

End result? A property that is publicly readonly, but privately readwrite without opening properties up to all of the fun fragility associated with categories."

Rose Perrone
  • 61,572
  • 58
  • 208
  • 243
8

Scott Stevenson (http://theocacao.com/) explains in his blog post "A Quick Objective-C 2.0 Tutorial: Part II" how to get Public Properties with Private Setters. Following his advice you will get a property that is read-only to the public, but has a private setter which can be used with the dot-syntax. Hope this helps...

f3lix
  • 29,500
  • 10
  • 66
  • 86
2

I just want to add my 2 cents and let people know that it IS possible to add properties to an existing class through Categories (not class extensions). It requires using associative references, but it's really not that bad.

I wrote a post about it here if anyone would like more details.

There is also another question that addresses this topic, but it's pretty scant on the details.

Cheers

Community
  • 1
  • 1
DougW
  • 28,776
  • 18
  • 79
  • 107
1

Because categories can only add methods to a class you can't get around this by trying to define property methods in the category.

You can declare properties that are derived from already existing classes. For example. If your class has a firstName and a lastName property, you can declare a property called fullName in a category @implementation.

@interface Bar (Private)
@property (readonly) NSString *fullName; // Note readonly, you have nothing to write to.
@end

However, you can't just @synthesize this property, you would have to write your own accessor because the compiler has no idea where you want to get this value from.

@implementation Bar (Private)
- (NSString *)fullName {
    NSString *returnString = [NSString stringWithFormat:@"%@ %@", 
                             self.firstName, self.lastName];
}

From a class design point of view, I'm not sure that the idea of a private property makes sense: I personally think of properties as something that are exposed publically by a class.

you could use the @private keyword in the BarLayer class to at least add some protection to its state.

Abizern
  • 146,289
  • 39
  • 203
  • 257
1

Actually, with the latest LLVM compiler, this problem can be solved much better. The previously recommended method to hiding as much about your properties as possible was to declare your variables in your .h, prefix them with an _, declare a property in a class extension in the private .m, and @synthesise that property in your @implementation.

With the latest LLVM (3.0), you can go farther yet, hiding everything about your property, including the backing ivar. Its declaration can be moved to the .m and even omitted there as well in which case it will be synthesized by the compiler (thanks Ivan):

Car.h:

@interface Car : NSObject

- (void)drive;

@end

Car.m:

@interface Car ()

@property (assign) BOOL driving;

@end

@implementation Car
@synthesize driving;

- (void)drive {

    self.driving = YES;
}

@end
lhunath
  • 120,288
  • 16
  • 68
  • 77