iosobjective-cfactorynscopyingnsmutablecopying

Objective-C pattern for creating mutable copies


I have many "model" objects whose properties are defined as "readonly" and shared among various components.

In some cases I need to create local mutable copies of the objects (using them for local mutable state)

I rather not implement NSMutableCopy protocol as the object should be immutable after it is created. The modified object could be "passed" around after copy+mutate operations.

Is there a suggested mechanism , or should I just implement a constructor receiving the "changed" parameters?

For example an object which parses a JSON to native types :

@interface ImmutableObject : NSObject
// various "readonly" properties
...
-(instancetype)initWithJSON:(NSDictionary *)jsonDictionary;

@property (nonatomic, readonly) MyClass1 *prop1;
@property (nonatomic, readonly) MyClass2 *prop2;
...
@property (nonatomic, readonly) NSArray<MyClass100 *>  *prop100;

@end

@implementation 
-(instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
  self = [super init];
  [self parseDictionaryToNative:jsonDictionary];
  return self;
}
@end

Somewhere in code:

ImmutableObject *mutated = [immutableObject mutableCopy]; // best way to accomplish this?
// change some values...
mutated.prop1 = ... // change the value to something new

self.state = [mutated copy]; // save the new object

Solution

  • @spinalwrap is correct, but in this case there is no reason to create the extra copy before storing it. NSMutableArray is a subclass of NSArray, so can be used anywhere an NSArray can be used (and this is very common). Same for yours. In your particular case, you'd probably do it this way:

    MutableObject *mutated = [immutableObject mutableCopy]; // create an instance of MutableObject
    
    mutated.prop1 = ... // change the value to something new
    
    self.state = mutated; // Since `state` is an immutable type, 
                          // attempts to mutate this later will be compiler errors
    

    This is safe because you know that this block of code is the only block that has a reference to the mutable version of the object (because you created it here).

    That said, once you've created a mutable subclass, you now need to consider the possibility that any ImmutableObject you are passed might actually be a MutableObject, and so make defensive copies (just as is done with NSArray, NSString, etc.) For example:

    - (void)cacheObject:(ImmutableObject *)object {
        // Need to copy here because object might really be a MutableObject
        [self.cache addObject:[object copy]];
    }
    

    This is made fairly efficient by implementing copy on ImmutableObject and return self, and implementing copy on MutableObject as an actual copy, usually like this:

    ImmutableObject.m

    - (ImmutableObject *)copy {
        return self;
    }
    

    MutableObject.m

    // as in spinalwrap's example
    - (MutableObject *)mutableCopy {
        MutableObject *instance = [MutableObject new];
        instance.prop1 = [self.prop1 copy]; // depends what you want here and what kind of class the properties are... do you need a deep copy? that might be a bit more work.
        // etc...
        return instance;
    }
    
    // No need to duplicate code here. Just declare it immutable; 
    // no one else has a pointer to it
    - (ImmutableObject *)copy {
        return (ImmutableObject *)[self mutableCopy];
    }
    

    So the copy is almost free if the object was immutable already. I say "fairly efficient" because it still causes some unnecessary copies of mutable objects that are never mutated. Swift's copy-on-write system for value types was specifically created to deal with this problem in ObjC. But the above is the common pattern in ObjC.