Skip to content

Objective-C 2.0 properties and to-many relationships

I’ve occasionally been asked about the appropriate form for properties representing to-many relationships in Objective-C 2.0.

Let’s start with the example of a recipe and its ingredients, represented by instances of the Recipe and Ingredient classes.

@interface Recipe : NSObject {
@private
    NSMutableSet *_ingredients;
}

@property (copy) NSSet *ingredients;

@end

This is a pretty straightforward interface for the Recipe class, but how should we actually implement it? You might first think of writing something like this:

@implementation Recipe

- (id)init {
    if (self = [super init]) {
        _ingredients = [[NSMutableSet alloc] init];
    }
    return self;
}

- (void)dealloc {
    [_ingredients release];
    [super dealloc];
}

@synthesize ingredients = _ingredients;

@end

However, this will not do what you expect. In particular, whenever you manipulate the ingredients property, it always replace the value of the _ingredients instance variable used for its storage with a new, immutable NSSet!

What’s wrong with this? For one thing, you won’t be able to make any finer-grained changes to the ingredients property, so your code may wind up doing a lot of work unnecessarily. You’ll only ever post Key-Value Observing changes for the entire property, as well, not for individual manipulations; anything observing those changes will probably wind up doing extra work, too.

Why not instead change the type of the property itself to NSMutableSet * then? That way, your code could just manipulate the ingredients of a recipe directly, right? You could do that, but then you wouldn’t get any Key-Value Observing notifications for changes to the property. Why not? Because KVO is all about notification of property changes, and changing the object that stores a property’s data isn’t the same thing as changing the property itself.

What should you do then? Here’s how I would implement the Recipe.ingredients property instead of using the above @synthesize directive:

@implementation Recipe (IngredientsProperty)

- (void)setIngredients:(NSSet *)value {
    [_ingredients setSet:value];
}

- (NSSet *)ingredients {
    return [NSSet setWithSet:_ingredients];
}

@end

What’s different here is that I’m taking advantage of the fact that the instance variable backing the property is mutable. For just a getter and a setter, that isn’t a big deal. However, since I’m dealing with a to-many relationship, I wouldn’t just write a getter and a setter. I’d also write some of the additional relationship-KVC methods for the property, so I can manipulate the property more efficiently, and get finer-grained KVO notifications:

@interface Recipe (IngredientsProperty)

- (void)addIngredientsObject:(Ingredient *)ingredient;
- (void)removeIngredientsObject:(Ingredient *)ingredient;

- (void)addIngredients:(NSSet *)ingredients;
- (void)removeIngredients:(NSet *)ingredients;

@end

@implementation Recipe (IngredientsProperty)

- (void)addIngredientsObject:(Ingredient *)ingredient {
    [_ingredients addObject:ingredient];
}

- (void)removeIngredientsObject:(Ingredient *)ingredient {
    [_ingredients removeObject:ingredient];
}

- (void)addIngredients:(NSSet *)ingredients {
    [_ingredients unionSet:ingredients];
}

- (void)removeIngredients:(NSSet *)ingredients {
    [_ingredients minusSet:ingredients];
}

@end

By doing this, when I need to manipulate a Recipe’s ingredients property I can use -mutableSetValueForKey: to do so and any changes I make will be efficient. For example, if I’m creating a Recipe to represent Meghan’s Butternut Squash Panang Curry, I might write some code like this:

NSMutableSet *ingredients = [panangCurryRecipe mutableSetValueForKey:@"ingredients"];

[ingredients addObject:[Ingredient ingredientWithName:@"Butternut Squash" quantity:1]];
[ingredients addObject:[Ingredient ingredientWithName:@"Panang Curry Paste" quantity:1]];
[ingredients addObject:[Ingredient ingredientWithName:@"Coconut Milk" quantity:1]];

Instead of making several copies of the set as I make changes, the underlying mutable set is changed in as efficient a way as possible given the accessors I’ve implemented. I don’t have to do any extra work to make that happen.

I also get efficient KVO change notifications for the property, so if I have any user interface bound to it — whether through Cocoa bindings or, if I’m using Cocoa Touch, a “bindings lite” implemented atop KVO — the change notifications it receives will reflect exactly the changes made, instead of wholesale replacement of the set.

I could still improve the code above. I’m using -[NSObject(NSKeyValueCoding) mutableSetValueForKey:] to manipulate the Recipe.ingredients property. That means I don’t get nice Code Sense completion from Xcode, and have to remember the property’s name and spell it correctly when I use it in a string. So I’ll add the following property declaration and implementation:

@interface Recipe (IngredientsProperty)
@property (readonly, copy) NSMutableSet *mutableIngredients;
@end

@implementation Recipe (IngredientsProperty)

- (NSMutableSet *)mutableIngredients {
    return [self mutableSetValueForKey:@"ingredients"];
}

@end

You’re probably thinking something like “Wait a minute, readonly and NSMutableSet?!” That’s exactly what I mean to say, though: You can mutate the collection you get back (“read”) from the property, but not the property itself.

Update: On Twitter, a couple of people asked why I didn’t just use -[Recipe addIngredientsObject:] directly, since I have that available. I certainly could have done that, and it’d have all of the advantages I cite, and it wouldn’t require the creation of the proxy mutable set either. However, if I wanted to something more complex than just an addition, using the proxy mutable set is a significant advantage.

This is because the proxy mutable set (or array, if you’re using an ordered relationship and -mutableArrayValueForKey:) will do the heavy lifting of figuring out the right combination of the accessors your implemented accessors to perform an operation most efficiently. Also, technologies like Cocoa bindings will always use the proxy.

With this additional property in place, the entire Recipe class will look something like this:

@interface Recipe : NSObject {
@private
    NSMutableSet *_ingredients;
}

@property (copy) NSSet *ingredients;
@property (readonly, copy) NSMutableSet *mutableIngredients;

- (void)addIngredientsObject:(Ingredient *)ingredient;
- (void)removeIngredientsObject:(Ingredient *)ingredient;

- (void)addIngredients:(NSSet *)ingredients;
- (void)removeIngredients:(NSet *)ingredients;

@end

@implementation Recipe

- (id)init {
    if (self = [super init]) {
        _ingredients = [[NSMutableSet alloc] init];
    }
    return self;
}

- (void)dealloc {
    [_ingredients release];
    [super dealloc];
}

- (void)setIngredients:(NSSet *)value {
    [_ingredients setSet:value];
}

- (NSSet *)ingredients {
    return [NSSet setWithSet:_ingredients];
}

- (NSMutableSet *)mutableIngredients {
    return [self mutableSetValueForKey:@"ingredients"];
}

- (void)addIngredientsObject:(Ingredient *)ingredient {
    [_ingredients addObject:ingredient];
}

- (void)removeIngredientsObject:(Ingredient *)ingredient {
    [_ingredients removeObject:ingredient];
}

- (void)addIngredients:(NSSet *)ingredients {
    [_ingredients unionSet:ingredients];
}

- (void)removeIngredients:(NSSet *)ingredients {
    [_ingredients minusSet:ingredients];
}

@end

And the code for creating the curry recipe becomes this, for which Xcode will give helpful Code Sense completion suggestions:

NSMutableSet *ingredients = panangCurryRecipe.mutableIngredients;

[ingredients addObject:[Ingredient ingredientWithName:@"Butternut Squash" quantity:1]];
[ingredients addObject:[Ingredient ingredientWithName:@"Panang Curry Paste" quantity:1]];
[ingredients addObject:[Ingredient ingredientWithName:@"Coconut Milk" quantity:1]];

As well, it continues to avoid making copies of the underlying collection representing the relationship, and it also continues to post fine-grained KVO change notifications rather than whole-property notifications, ensuring bound controls are updated efficiently.

So when you’re creating properties for to-many relationships whether they’re unordered (NSSet) or ordered (NSArray), consider using this approach to implementing them. It’ll take a little more code, but it’ll be a lot more efficient and more correct.

Bonus Round: Core Data

What about Core Data? Now that iPhone OS 3.0 has Core Data, in addition to Mac OS X, there’s really no excuse not to use it. But would you do anything differently above?

Of course. But since we’re talking about Core Data, it turns out that what you do different is actually write a whole lot less code. Here’s what the declaration of the Recipe class will look like if it corresponds to a Core Data entity:

@interface Recipe : NSManagedObject

@property (copy) NSSet *ingredients;
@property (readonly, copy) NSMutableSet *mutableIngredients;

@end

@interface Recipe (CoreDataGeneratedAccessors)

- (void)addIngredientsObject:(Ingredient *)ingredient;
- (void)removeIngredientsObject:(Ingredient *)ingredient;

- (void)addIngredients:(NSSet *)ingredients;
- (void)removeIngredients:(NSet *)ingredients;

@end

Notice that I’ve gotten rid of the instance variables section entirely. This is because Core Data manages the storage for your modeled attributes and relationships for you; you don’t need (and really don’t want) instance variables for them.

You’ll also notice that I put the additional to-many relationship accessor methods in their own category. To see why, take a look at the implementation of the class:

@implementation Recipe

@dynamic ingredients;

- (NSMutableSet *)mutableIngredients {
    return [self mutableSetValueForKey:@"ingredients"];
}

@end

Notice anything missing? All of the methods related to the modeled ingredients property! Core Data will not only generate an efficient setter and getter for the ingredients property automatically at run time, but will also generate implementations for the other ingredients to-many relationship accessor methods as well!

Core Data will generate the methods (at the latest) when you try to use them; it’s not dependent on having the category declaration available. That’s just for the compiler and IDE’s benefit when you’re writing code that uses those methods, so they can be completed by Code Sense and the compiler knows not to generate unknown-method warnings.

Changes

I added a bit after the first use of -mutableSetValueForKey: to address why one might want to use the mutable set proxy rather than just using the finer-grained KVC accessor methods directly.

{ 6 } Comments