3

I was planning to use an NSMutableDictionary property for storing game data (such as score, settings, etc...).

@property (nonatomic, copy) NSMutableDictionary *gameData;

In researching why there is no "mutablecopy" option for properties I found this discussion, for which the accepted answer states:

the right way to do it is not to make the mutable array a property

What then is the best way to handle mutable collections as properties in modern Objective-C?

Community
  • 1
  • 1
firyice
  • 527
  • 7
  • 24

3 Answers3

7

It is quite a seldom pattern for a class to expose a mutable property for a collection object as it seems to break encapsulation.

Nevertheless there are occasions when it might make sense: You could, for example return a KVO proxy for some internally managed collection (using KVC's mutableArrayValueForKey:).

In these cases is does not really matter whether or not to declare the getter as a property or plain method. In case of properties it should not be a copy property, though.

To make this a little clearer here's an example. We have a class that declares a single public getter:

@interface Foo : NSObject

@property (strong, readonly) NSMutableArray *publicBars;

@end

The implementation manages an encapsulated, private mutable array named _bars:

@implementation Foo
{
    NSMutableArray *_bars;
}

The elements of this array are exposed by KVC to-many accessors:

- (NSUInteger)countOfBars
{
    return [_bars count];
}

- (id)objectInBarsAtIndex:(NSUInteger)idx
{
    return _bars[idx];
}

- (void)insertObject:(id)object inBarsAtIndex:(NSUInteger)idx
{
    if (_bars == nil)
        _bars = [NSMutableArray array];
    [_bars insertObject:object atIndex:idx];
}

- (void)removeObjectFromBarsAtIndex:(NSUInteger)idx
{
    [_bars removeObjectAtIndex:idx];
}

Here's the fancy part: We are implementing the public property by returning a KVC proxy that reflects and mutates the internal state without exposing internal ivars. It changes the internal array by only using the public accessors defined above.

- (NSMutableArray *)publicBars
{
    return [self mutableArrayValueForKey:@"bars"];
}

@end

We can use the proxy to change the internal collection:

Foo *foo = [[Foo alloc] init];

NSMutableArray *bars = foo.publicBars;

[bars addObject:@1];
[bars addObject:@2];
[bars removeObjectAtIndex:0];

 // Now the internal _bars array is @[ @2 ].

There are actual examples of this and similar patterns in Cocoa. For example there is -[NSTextView textStorage] that returns the internal backing store (an object derived from NSMutableAttributedString). Though not a collection object this is a mutable object which passes mutations to its host object, the text view.

Nikolai Ruhe
  • 81,520
  • 17
  • 180
  • 200
  • I like your idea, but it won't generate KVO notifications for the additions and removals to keyPath publicBars. It will generate KVO notifications for keyPath bars though. I struggle with this myself - how to document that a class has a KVC and KVO compliant collection property without actually declaring a property in the interface. If you had methods like addBars and removeBar in the interface, that might give you a spot to place documentation about the KVO/KVC property, but what if you just want others to be able to observe changes to the collection of bars but not make those changes? – Joel Jan 02 '15 at 07:09
  • @Joel Good point. There's no common way to declare KVO compliancy in the header. Im usually going with a comment but it might be nice to have a more "standard" way, like an `NSKVOCompliance` attribute or similar. The static analyzer might even use this to produce warnings for bad observations. – Nikolai Ruhe Jan 02 '15 at 07:34
1

IMHO, you can very well use a mutable property - as properties in Objective-C are not only used for encapsulation, but also for memory management (by ARC).

However, you should keep this property in your implementation file, exposing it as a non-mutable, readonly piece of it to other classes.

//your_header_file.h

@interface YouClass : NSObject

@property (readonly) NSDictionary *gameState;
@end

//your_implementation_file.m

@interface YouClass ()
@property (nonatomic, strong) NSMutableDictionary *gameState;
@end

@implementation SomeClass

//deal with mutable state here


@end

EDIT :

Also, as mutable collections are usually non thread-safe, be sure you always change them from same thread.

Vinzzz
  • 11,746
  • 5
  • 36
  • 42
1

The problem with mutable properties is that other classes can change the contents of the collection without the owner noticing. A good design sees that only one class is responsible for a specific task, in this case storing game data.

What I would do is make the mutable dictionary a strong private property and provide accessor methods to the outside world in the form of:

- (void)storeObject:(id)object forSetting:(NSString *)settingName;
- (id)objectForSetting:(NSString *)settingName;

If you really need external classes to be able to set the dictionary as a whole, then -- after making sure this really is necessary -- you can still make it a non-mutable property which, when set, creates a new instance of the internal mutable dictionary from the items in the nonmutable dictionary. But I think you should be able to engineer around that necessity.

Pascal
  • 16,846
  • 4
  • 60
  • 69