简体   繁体   中英

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

note that NSMutableArray , NSMutableData etc. are different classes than their immutable counterparts. So in this case, you maybe should define a MutableObject class with the same interface as the ImmutableObject class (but with mutable properties) and use that if you want to have a mutable object.

MutableObject *mutated = [immutableObject mutableCopy]; // create an instance of MutableObject

mutated.prop1 = ... // change the value to something new

self.state = [mutated copy]; // creates an ImmutableObject

the implementation of ImmutableObject's mutableCopy could be something like:

- (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;
}

and MutableObject's copy method could look like this:

- (ImmutableObject *) copy
{
    ImmutableObject *instance = [ImmutableObject new];
    instance.prop1 = [self.prop1 copy];
    // etc...
    return instance;
}

You're not forced to use the NSMutableCopy protocol formally, but you can.

@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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM