さて、NSString
をプロパティとして保持すること、たくさんありますよね。Googleのコーディング規約にもcopyにするよう書いてあります。
自分もそうしていたのですが、なぜそうしなければいけないのかというところまで考えないエンジニア失格状態だったのでちゃんと考えてみました。
さて、そもそもcopy
メソッドとは何なのか?
##copyメソッド
copy
メソッドはNSObject
で定義されているのですが、copy
メソッドを呼ぶとcopyWithZone:
メソッドが内部的に呼ばれるようになっています。
APIリファレンスを見るとしっかりと書かれています。
Return Value
The object returned by the NSCopying protocol method copyWithZone:,.
copyWithZone:
メソッドはNSCopying
プロトコルのメソッドです。ただしNSObject
はcopy
メソッドの定義はしていますがNSCopying
プロトコルは採用していません。copyWithZone:
が実装されていない場合には例外が発生します。
##copyWithZone:メソッド
前述の通りNSCopying
プロトコルのメソッドです。
- (id)copyWithZone:(NSZone *)zone
引数はNSZone型のオブジェクトです。ゾーンは以前、実行効率を向上されるために使用されていたそうですが、もう使われていないとのこと(ゾーンについてはこちらを参照)。現在はcopy
メソッドが呼ばれた際は引数にNULL
を指定して自身のcopyWithZone:
を呼び出すようになっています。
copyWithZone:
についてAPIリファレンスを見てみると、このようなことが書かれています。
The copy returned is immutable if the consideration “immutable vs. mutable” applies to the receiving object; otherwise the exact nature of the copy is determined by the class.
immutableなオブジェクトを返すようになっています。ただcopy
の振る舞いはクラスによって変わってくるようです。
##プロパティのcopyについて
copy
がクラスによって振る舞いを変えるということが分かりました。
ここでプロパティのオプションにcopy
を指定すると、内部的にどのような処理が行われるか見てみましょう。
- (void)setName:(TYPE)obj {
if (name != obj) {
name = [obj copy];
}
}
copy
を指定した場合、このようにセッタが作られます。copyWithZone:
を考慮すると、mutableなオブジェクトがセットされるとimmutableなオブジェクトになります。
##NSStringのcopyメソッド
ここでようやくNSString
に触れられます。NSString
クラスのインスタンスにcopy
を送っても同じアドレスを指すようになっています。
NSString *string = NSStringFromClass([self class]);
NSLog(@"%p",string); // 0x8dbb950
NSLog(@"%p",[string copy]); // 0x8dbb950
そもそもimmutable = 不変なオブジェクトで変わることは考えられないので、わざわざコピーを作る必要はないということなのでしょう。つまりimmutableなオブジェクトが渡された場合、(内部的にcopy
は呼ばれるが)結果的にstrong
と同じ動作になります。
NSMutableString
の場合は、通常のコピーが行われます。
##NSMutableStringによる意図しない変更
ではプロパティのオプションをstrong
にした場合に何が困るのでしょうか?
プロパティのオプションをstrong
にした場合、参照しているNSMutableString
オブジェクトの値が変更されると、プロパティの値も変更されてしまい、意図しない変更が起きてしまいます。
それぞれプロパティを用意して試してみましょう。
@interface ViewController ()
@property (nonatomic, strong) NSString *strongString;
@property (nonatomic, copy) NSString *copiedString;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableString *mutableString = [[NSMutableString alloc] initWithString:@"one, two, three"];
self.strongString = mutableString;
self.copiedString = mutableString;
[mutableString appendString:@", four"];
NSLog(@"%@",self.strongString);
// one, two, three, four
NSLog(@"%@",self.copiedString);
// one, two, three
}
上記の例ではself.strongString
はmutableString
と同じアドレスを指しているので、mutableString
が変更されたことによりself.strongString
も変更されてしまっています。
self.copiedString
の方は、copy
が行われ新たなメモリ領域が作られます。よってmutableString
の変更の影響は受けません。
プロパティにcopy
オプションを指定しておけば、NSMutableString
の代入を行って意図しない変更が発生する心配をせずに済みますね。
##まとめ
以上、ここまでNSString
について書きましたが、immutableなクラスのcopy
がインスタンス自身を返すようになっていたり、mutableなクラスのcopy
の返り値がimmutableなのはNSArray
やNSDictionary
も同様です。
まとめるとこんな感じになります。
1.NSString
やNSArray
などのimmutableなクラスのcopy
メソッドは同じアドレスを指すようになっている。よってstrong
でもcopy
でも動作は変わらない。
2.NSMutableString
のcopy
メソッドは、immutableなオブジェクトが返される。
mutableなクラスを渡されないことは保証できない、immutableなオブジェクトの場合はプロパティにcopy
を指定してもstrong
と変わらない動作をする。
これがcopy
オプションを選択すべき理由です。