objective-cclonemutation

Obj-C: Clone object with a mutated property


I would like to a create convenience initiator by cloning an existing object with a mutation, while keeping the original object intact.

For example:

  1. Given a Person object person1 with a name (Tom) and age (10)

  2. I would like to clone the person1 object, but with 0 age.

I have following code in Obj-C, but not sure if there's a better way to do it:

Person.h

@interface Person : NSObject
@property (nonatomic, readonly) NSString *name;
@property (nonatomic, readonly) NSUInteger age;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;

- (instancetype)cloneWithZeroAge;
@end

Person.m

@implementation Person

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age
{
    if (self = [super init]) {
        _name = name;
        _age = age;
    }
    return self;
}

- (instancetype)cloneWithZeroAge
{
    if (self) {
        // mutate age to 0
        return [self initWithName:_name age:0];
    }
    return self;
}


@end

Solution

  • Let's start with the constructor. For you specific scenario it doesn't make much difference, but in order to be functionally independent, you better ensure that the name gets a copy of the data passed. You also want to specify this part as memory storage modifier of the property, so the contract is apparent to the client code:

    @interface Person : NSObject
    @property (copy, nonatomic, readonly) NSString *name;
    ...
    @end
    
    @implementation Person
    
    - (instancetype)initWithName:(NSString *)name age:(NSUInteger)age
    {
        if (self = [super init]) {
            _name = [name copy];
            _age = age;
        }
        return self;
    }
    

    Now for the actual "copy" method. In order to be more consistent with existing NSCopying protocol and Objective-C naming convention, and, which is more important, the memory management ownership convention, you should start the method name with the word "copy", so the calling side knows it's responsible for releasing the object. The most important part now, is that if you want to keep the original object untouched, you have to allocate and create a new object. In your implementation, however, you just change the self into the new object entirely. Here is how I would implement such a method:

    - (instancetype)copyWithZeroAge {
        Person *copy = [[Person alloc] initWithName:_name age:0];
        return copy;
    }
    

    If you prefer to keep the name property memory modifier strong instead of copy, don't forget to copy the instance:

    - (instancetype)copyWithZeroAge {
        Person *copy = [[Person alloc] initWithName:[_name copy]
                                                age:0];
        return copy;
    }