1

There are many questions concerning the category-properties problem. I know some possibilities to address this:

From my point of view both is not clean since the memory allocated is never cleared when the object that created such properties is deallocated.

Categories are a good way to keep code clean and dynamically add functionality to already existing classes. They help to group functionality and to distributed implementation work among more developers.

The bad about categories is the missing storage.

I came across this problem several times now and I'm wondering whether the following would address this problem in an clean way that also takes care about the memory and if there are any problems that I can't see right now.

There is one restriction, that I can ignore since I'm working as a framework developer: I'm able to create my own root class that all my other classes can inherit from.

First of all declare the new root object:

@interface RootObject : NSObject

- (void)setRuntimeProperty:(id)runtimeProperty forKey:(id<NSCopying>)key;
- (id)runtimePropertyForKey:(id)key;

@end

With the corresponding implementation:

#import "RootObject.h"

@interface RootObject ()
@property (readwrite) NSMutableDictionary *runtimeProperties;
@end

@implementation RootObject
@synthesize runtimeProperties = _runtimeProperties;

- (id)init {
    self = [super init];

    if (self)
    {
        _runtimeProperties = [[NSMutableDictionary alloc] initWithCapacity:1];
    }

    return self;
}

- (void)dealloc {
    [_runtimeProperties release];
    _runtimeProperties = nil;

    [super dealloc];
}

- (id)runtimePropertyForKey:(id)key {
    return [self.runtimeProperties objectForKey:key];
}

- (void)setRuntimeProperty:(id)runtimeProperty forKey:(id<NSCopying>)key {
    if (key)
    {
        if (runtimeProperty)
        {
            [self.runtimeProperties setObject:runtimeProperty forKey:key];
        }
        else
        {
            [self.runtimeProperties removeObjectForKey:key];
        }
    }
}

@end

By using this RootObject instead of NSObject it should be very easy to add a "property" to a category on a class. Consider having some class MyClass

@interface MyClass : RootObject
// some interface here
@end

When implementing a special behavior on top of this class you are now able to add a property like this:

@interface MyClass (specialBehavior)
@property (nonatomic, retain) NSString *name;
@property (nonatomic, copy) NSDate *birthday;
@end

With corresponding implementation:

@implementation MyClass (specialBehavior)
@dynamic name;
- (NSString *)name {
    return [self runtimePropertyForKey:@"name"];
}
- (void)setName:(NSString *)name {
    [self setRuntimeProperty:name forKey:@"name"];
}
@dynamic birthday;
- (NSDate *)birthday {
    return [self runtimePropertyForKey:@"birthday"];
}
- (void)setBirthday:(NSDate *)birthday {
    [self setRuntimeProperty:[birthday copy] forKey:@"birthday"];
}
@end

Such an implementation could KVO compatible as well by just adding the necessary calls in the setter method. Very straight forward, but I'm wondering whether I missed something important? (E.g. very very bad runtime performance having many such declared properties or using many of these objects)

Community
  • 1
  • 1
Enigma
  • 125
  • 4
  • If you're developing the classes in question and just using categories for organization, why not add the necessary properties to your classes directly? The storage "problem" with categories only applies when you aren't able to change the class definition. – Caleb Dec 16 '13 at 15:08
  • @Caleb: Things are not as simple as shown here. By directly changing `MyClass` it would grow and grow with every special behavior implementation. And back to the framework development; behaviors would get tied to `MyClass` even if such behaviors are only used in a special project. So my goal is to have slim classes with root functionality only. – Enigma Dec 16 '13 at 15:17
  • *grow and grow with every special behavior* Well, yes, that's what happens when you add properties to a class. *even if such behaviors are only used in a special project* So why add those behaviors to the class itself? Subclassing seems like a reasonable solution to both these concerns. If your solution work for you, then great (but read Jesse's answer first). – Caleb Dec 16 '13 at 15:27

1 Answers1

6

This is effectively the same as objc_setAssociatedObject and objc_getAssociatedObject, which do release memory when the object is deallocated (depending on the association type). I would guess they also have much lower overhead than your suggested code.

Jesse Rusak
  • 56,530
  • 12
  • 101
  • 102
  • I quick implemented a test to verify this. I remember that we had an issue in an earlier stage of development that memory was not cleared. But all works as expected in my test now so we might did something wrong in the past. Also KVO comes out of the box using [such an implementation](http://oleb.net/blog/2011/05/faking-ivars-in-objc-categories-with-associative-references/). Thanks! – Enigma Dec 16 '13 at 15:55