objective-cpropertiesaccessorivar

Do I need to override the getter for a property to return an immutable copy?


Suppose that I have a class that holds a mutable array. I want to make sure that if other classes ask for the array they will get a non-mutable type, but in the owning class, it is actually an instance of NSMutableArray, so that I can add and remove items.

#import "Person.h"

@class Asset;

@interface Employee : Person

{
    NSMutableArray *_assets;
}

@property (nonatomic,copy) NSArray *assets;

-(void)addAssets:(Asset *)a;

The question is, do I have to modify the accessor methods into something like this, or will it automatically behave like I want?

#import "Employee.h"
#import "Asset.h"

@implementation Employee

/* Accessors for assets properties
-(NSArray *)assets
{
    return [_assets copy];
}

-(void)setAssets:(NSArray *)assets
{
    _assets = [assets mutableCopy ];

}
*/

-(void)addAssets:(Asset *)a
{
    //is assets nil?

    if (!_assets) {
        //Create Array
        _assets = [[NSMutableArray alloc]init];
    }
    [_assets addObject:a];

}

Solution

  • ppalancica's answer is incorrect. The copy attribute means only that the setter will take a copy when the property is set. The synthesized getter will not return a copy. You must implement that behavior yourself:

    - (NSArray *)assets
    {
        return [_assets copy];
    }
    

    You might want to make an internal-only accessor that doesn't make a copy. You could also redeclare the property privately; client code will then be contracted to treat the array it requests as immutable.

    This code demonstrates that the synthesized getter returns the uncopied object:

    #import <Foundation/Foundation.h>
    
    @interface ArrayReturner : NSObject
    
    @property (copy, nonatomic) NSArray * array;
    
    @end
    
    @implementation ArrayReturner
    {
        NSMutableArray * _array;
    }
    
    - (BOOL)myArrayIsIdenticalTo:(NSArray *)otherArray
    {
        return _array == otherArray;
    }
    
    @end
    
    int main(int argc, const char *argv[])
    {
        @autoreleasepool {
    
            ArrayReturner * a = [ArrayReturner new];
            [a setArray:@[@1, @2]];
            NSArray * returnedArray = [a array];
            // Does not throw
            NSCAssert([a myArrayIsIdenticalTo:returnedArray],
                      @"Returned array is a separate instance.");
        }
    
        return 0;
    }