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

  1. jens@mooseyard.com | June 29, 2009 at 7:23 am | Permalink

    This is the point where I always start to get fed up with KVO. A very simple relationship, “a Recipe has a mutable Set of Ingredients”, which should ideally take one line of source code to express, explodes into something like 50 lines of boilerplate. I could use a utility like Accessorizer to write that code for me, but it’s still littering my source and bloating my app.

    To be honest, it’s a total flashback to JavaBeans.

    Also, the optimization using mutable-set proxy property (mutableIngredients) is clever, but it stil bothers me that the property name ends up being different — I access the mutableIngredients property, but I can’t observer that property, I have to observe ‘ingredients’ instead. Back when I was first learning KVO I tried various ways to tweak this so there was only a single property name, but it never worked right.

    It would be great if this could somehow be cleaned up in the future. Even if it requires having the Obj-C runtime generate all that boilerplate synthetically.

  2. eschaton | July 1, 2009 at 11:36 am | Permalink

    It was cleaned up in Leopard, if you use Core Data: You don’t even need to implement the accessors, and you can have the modeling tools generate accessor declarations for you.

    And really, especially now with iPhone OS 3.0, there’s no reason not to use Core Data.

  3. Brian Hardy | July 8, 2009 at 6:33 am | Permalink

    Nice post. There is one small typo in the method declarations:

    • (void)removeImages:(NSet *)ingredients;

    (Should be NSSet)

  4. Kevin Callahan | July 31, 2009 at 12:29 pm | Permalink

    Yeah – I’ve had accessor support in Accessorizer for ordered collections and recently added support for unordered collections per request (Scott Anguish). You will note there’s a check-box in the collections pane in Accessorizer for Custom to-many relationship where Accessorizer will wrap your code with -will/didChangeValueForKey: … and provide the appropriate withSetMutation:NSKeyValueUnionSetMutation etc. Accessorizer will write all that code for you saving you a ton of time. Check out the videos online at the Accessorizer home page.

    Yeah, your class gets big, but you can fold those methods and tuck them away in XCode so that you don’t see the clutter.

  5. Rob Rix | August 25, 2009 at 11:02 am | Permalink

    I’m curious now that you’ve mentioned -mutableArrayValueForKey:. What would the indexed accessors look like, specifically? (Their prototypes, I mean.)

  6. Steven Degutis | October 17, 2009 at 9:30 am | Permalink

    s/of the accessors your implemented accessors/of your implemented accessors/ ?

Post a Comment

Your email is never published nor shared. Required fields are marked *