MarcWan
MarcWan

Reputation: 2973

Objective-c readonly copy properties and ivars

I'm try to grok properties declared as both copy and readonly in objective-c, and specifically, whether I have to do the copy myself. In my init methods. Evidence suggests I do:

@interface A : NSObject 
    @property(nonatomic, copy, readonly) NSData *test;
    - (instancetype)initWithData:(NSData *)data;
@end

@implementation A

- (instancetype)initWithData:(NSData *)data {
    if ((self = [super init]) != nil) {
        _test = data;
    }
    return self;
}

@end


int main (void) {
    NSData *d1 = [NSMutableData dataWithBytes:"1234" length:5];
    A *a = [[A alloc] initWithData:d1];

    NSLog(@"%lx", (unsigned long)d1);
    NSLog(@"%lx", (unsigned long)a.test);
    return 0;
}

I had thought I could do self.test = data in my init method, but that is not permitted because it's readonly (not unexpectedly). Of course, self.test = [data copy] ensures two different objects.

So: Is there a way to create a readonly property in objective-c that copies the incoming value, or is it sufficiently an edge case that the combination is pointless and I have to do any copying myself manually anyway?

Upvotes: 0

Views: 492

Answers (2)

Ken Thomases
Ken Thomases

Reputation: 90551

In addition to what Darren said, the copy attribute describes what semantics the properties setter has. In your initializer, you're not using the setter, you're directly assigning to the instance variable.

It's maybe a bit hard to grok, but the instance variable is not the same thing as the property. It is used to implement the property in this case. But, assigning to the instance variable is not the same as setting the property.

If you want your initializer to also have the semantics that it copies the passed-in data, that's a separate design decision (although a good idea to go with the property's semantics). You could implement that by using a private setter as Darren suggests, but you could also just do:

    _test = [data copy];

in the initializer.

Upvotes: 0

Darren
Darren

Reputation: 25619

A @property declaration is merely shorthand for some accessor/mutator method declarations, and (in some cases) synthesized implementations for said accessor/mutator methods.

In your case, the @property(nonatomic, copy, readonly) NSData *test declaration expands to this equivalent code:

@interface A : NSObject
{
    NSData* _test;
}
- (NSData*)test;
@end

@implementation A
- (NSData*)test
{
    return _test;
}
@end

There is no setTest: mutator method because the property is declared as readonly, so the copy attribute has no effect.

You can implement your own mutator method:

- (void)setTest:(NSData*)newValue
{
    _test = [newValue copy];
}

Or, you can have the compiler synthesize a mutator method for you by declaring a read/write property in a private class extension in your implementation file:

// A.m:

@interface A() 
@property (nonatomic, copy) NSData* test;
@end

Both cases would allow you to use the test mutator method to copy a value to the _test instance variable:

- (instancetype)initWithData:(NSData *)data {
    if ((self = [super init]) != nil) {
        self.test = data;
    }
    return self;
}

The end result is:

@interface A : NSObject
@property(nonatomic, copy, readonly) NSData* test;
- (instancetype)initWithData:(NSData*)data;
@end

@interface A()
@property (nonatomic, copy) NSData* test;
@end

@implementation A
- (instancetype)initWithData:(NSData*)data {
    if ((self = [super init]) != nil) {
        self.test = data;
    }
    return self;
}
@end

Upvotes: 1

Related Questions