nazolabo

フリーランスのWebエンジニアが近況や思ったことを発信しています。

@propertyの属性の挙動の違い

@propertyの属性にcopyとかstrongとかweakとかあるけど、概念的な説明はどこでもあるけど、具体的な説明をしている記事が少ないので調査した。

Xcode4.3.2時点での内容です。

nonatomic

非スレッドセーフにする。マルチスレッドのものを使わない限りはどんどん指定しよう。
ちなみにatomicな場合は内部的にはgetterは以下のような処理になる模様。ロックが入ってしまう。

[_internal lock];
id result = [[value retain] autorelease];
[_internal unlock];
return result;

getter/setter

getterとsetterのメソッド名はデフォルトで、getterは変数名と同じ、setterはset[Capitalize変数名]だが、それを任意のものに設定することができる。
readonly時にsetterは指定できない(当たり前だが)
通常変更する必要はないが、isなんたら とかってgetterにしたい場合に使うといいらしい。

readwrite/readonly

readwriteはデフォルトなので、単品で指定することはまずない。readonlyはその名の通りsetterが生成されなくなる。self.prop = 1;みたいなドットでのアクセスすらコンパイルエラーになるので、このままだと何もできない変数になる気がする。
.hではreadonlyにして、.mでreadwriteにして定義を上書きすることで、クラス内部では読み書きができ、外部では読み出ししかできないプロパティが出来上がる。

.h

#import <Foundation/Foundation.h>

@interface TestData : NSObject

@property (nonatomic, readonly) NSString* title;

-(void)action;

@end

.m

#import "TestData.h"

// ここがないとactionではエラー
@interface TestData ()
@property (nonatomic, readwrite) NSString* title;
@end

@implementationTestData

@synthesize title;

-(void)action {
    self.title = @"hello";
}

@end

strong/copy/retain/assign

  • ARCを使わない場合はassignがデフォルト、使う場合はstrongがデフォルト。
  • copyとreleaseは、以前の値に対しreleaseが呼ばれる。
  • retainは参照カウントが保持されて、assignは保持されないらしい。
  • copyはNSCopyingプロトコルが実装されていないオブジェクトに対して設定すると落ちる。NSObjectだけだと実装されていないので、使用する際は注意。
  • ARCを無効にしてテスト

TestString.h

#import <Foundation/Foundation.h>

@interface TestString : NSObject <NSCopying>
@property (nonatomic, copy) NSString* string;
-(id)initWithString:(NSString*)string;
@end

TestString.m

#import "TestString.h"

@implementation TestString
@synthesize string;

-(id)initWithString:(NSString*)newString {
    self = [super init];
    self.string = newString;
    return self;
}

- (void)dealloc {
    NSLog(@"dealloc %@", self.string);
    [super dealloc];
}

- (id)copyWithZone:(NSZone *)zone
{
    NSLog(@"duplicate %@", self.string);
    TestString* clone = [[TestString alloc] init];
    [clone setString:self.string];
    return clone;
}

@end

TestData.h

#import <Foundation/Foundation.h>

#import "TestString.h"

@interface TestData : NSObject
@property (nonatomic, [ここを任意に変更]) TestString* title;
@end

TestData.m

#import "TestData.h"

@implementation TestData

@synthesize title;

@end

上記のようなコードがあった場合、[ここを任意に変更]が以下を指定した場合に、

    TestData* data = [[TestData alloc] init];
    TestString* string = [[TestString alloc] initWithString:@"abc"];
    TestString* string2 = [[TestString alloc] initWithString:@"def"];
    [data setTitle:string];
    [data setTitle:string2];
    NSLog(@"end");
    [string release];
    [string2 release];   
    [data release];

というコードを動かした場合、以下のような結果になる。(コメントはその行を通過直後に発生されるログ)
strongは、最後に代入したstring2の所有権がdataに移っているので、外からstring2をreleaseしてもデストラクタが呼ばれない。
copyはcopyなので、外でreleaseするとデストラクタが呼ばれる。またcopyは、自分自身が入れ替わると、入れ替わる前の値はreleaseされるので、setTitle:string2でもデストラクタが呼ばれている。
assignは内部でどう扱おうが知ったことではないという感じ。外でreleaseするとdataのtitleもreleaseされているので、テストコードで言うところの、[data release];の直前でdataのtitleを扱おうとするとEXC_BAD_ACCESSとなる。参照だけを単純に代入すると考えるといい。あとスカラー型はassignでもコピー。
retainは基本的にstrongと同じ。内部的にretainというメソッドがあり、それを使って、「hoge = [fuga retain]」のように代入すると、strongと同じような効果が得られるため、そういうものがあるらしい。

copy
    TestData* data = [[TestData alloc] init];
    TestString* string = [[TestString alloc] initWithString:@"abc"];
    TestString* string2 = [[TestString alloc] initWithString:@"def"];
    [data setTitle:string];             // duplicate abc
    [data setTitle:string2];          // duplicate def, dealloc abc
    NSLog(@"end");                    // end
    [string release];               // dealloc abc
    [string2 release];               // dealloc def
    [data release];
strong
    TestData* data = [[TestData alloc] init];
    TestString* string = [[TestString alloc] initWithString:@"abc"];
    TestString* string2 = [[TestString alloc] initWithString:@"def"];
    [data setTitle:string];
    [data setTitle:string2];
    NSLog(@"end");               // end
    [string release];               // dealloc abc
    [string2 release];   
    [data release];

assign

    TestData* data = [[TestData alloc] init];
    TestString* string = [[TestString alloc] initWithString:@"abc"];
    TestString* string2 = [[TestString alloc] initWithString:@"def"];
    [data setTitle:string];
    [data setTitle:string2];
    NSLog(@"end");               // end
    [string release];                // dealloc abc
    [string2 release];             // dealloc def
    [data release];
retain
    TestData* data = [[TestData alloc] init];
    TestString* string = [[TestString alloc] initWithString:@"abc"];
    TestString* string2 = [[TestString alloc] initWithString:@"def"];
    [data setTitle:string];
    [data setTitle:string2];
    NSLog(@"end");               // end
    [string release];               // dealloc abc
    [string2 release];   
    [data release];

weak

weakはARCを有効にしないと使えない。assignと同等。