copyしたらreleaseを忘れずに
戯れ言が長くなるので結論だけ先に書きます。
タイトルの通りで、copyしたらreleaseかautoreleaseしなければならないということです。
考えてみれば当たり前のことですが、案外忘れがちかと思います。
-(NSArray*)array { NSMutableArray* ary = [NSMutableArray array]; [ary addObject:@"hoge"]; // ... return [ary copy]; // Potential leak of an object }
XcodeのAnalyzeにかけてやれば、このような単純なミスはすぐに発見できます。
上記のような場合は、autoreleaseが適切でしょう。
-(NSArray*)array { NSMutableArray* ary = [NSMutableArray array]; [ary addObject:@"hoge"]; // ... return [[ary copy] autorelease]; }
Objective-Cの経験の浅い人がチームにいる場合、メモリ管理のお約束事に入れてしまうと良いと思います。
(ARCを使わない場合)
・initしたらreleaseする。
・retainしたらreleaseする。
・copyしたらreleaseする。 <- new!
releaseするか、autoreleaseにするかは、
・そのメソッド内でしか使わないなら、init/retain/copyと同時にautoreleaseする。
・クラスのインスタンス変数に保持するなら、dealloc内(もしくは適切な破棄のタイミング)でreleaseする。
※dealloc以外で解放するならば、解放の直後にnilを代入しておくのが安全。
以下、copyとretainのお話
そもそもcopyとは何か
「オブジェクトのコピーを作ること」です。
retainは、「オブジェクトを保持すること」であり、具体的にはオブジェクトの参照数を1増やします。
NSArray* myArray = [ary retain]; // オブジェクトaryの参照数が1増えます [myArray release]; // myArray(==ary)の参照数が1減ります(0になったらdeallocされます)
copyは、retainとは違って、同じオブジェクトを参照するのではなく、オブジェクトのコピーを生成します。
NSArray* myArray = [ary copy]; // sの参照数を増やさずに、新しいコピーオブジェクトを生成します [myArray release]; // myString(!=s)の参照数が1減ります(0になり、deallocされます)
※実際には、aryの実体がNSMutableArrayでなく、NSArrayであれば、copyはretainと全く同じ動作をします。
retainとcopyどっちを使えばいいの?
私も以前だいぶ悩んだのですが、どうも、「値クラス」にはコピーを使う方が一般的なようです。
参考:某スタックオーバーフローの問答
今、「値クラス」という適当な言葉を作ってしまいましたが、具体的には、
・NSString
・NSDate
・NSArray
・NSDictionary
などのことです。
自作クラスをここでいう「値クラス」にするには、NSCopyingプロトコルを実装します。
関連する話として、NSDictionaryのキーにするオブジェクトは、NSCopyingプロトコルを実装していなければなりません。
retainに対するcopyのメリットってなに?
本質的に、copyよりもretainの方が高速です。新たにオブジェクトを生成するよりも、同じオブジェクトの参照数を操作するだけの方がコストがかからないからです。では、なぜcopyを使うのか。その方が安全な場合が多いからです。
例えば、以下のように、NSStringをretainプロパティにしてみます。
// 文字列プロパティのみを持つ単純なクラス @interface MyObject : NSObject @property(retain) NSString* myText; @end @implementation MyObject @synthesize myText; @end int main(int argc, const char** argv) { @autoreleasepool { NSMutableString* s = [NSMutableString stringWithString:@"my name is pizza."]; MyObject* myObj = [[[MyObject alloc] init] autorelease]; myObj.myText = s; [s deleteCharactersInRange:NSMakeRange(11, 6)]; [s appendString:@"Linus Torvalds"]; NSLog(@"%@", s); //=>my name is Linus Torvalds. NSLog(@"%@", myObj.myText); //=>my name is Linus Torvalds. } }
元の文字列sも、myObj#myTextプロパティも、同じ文字列に書き換えられてしまいました。
これは多くの場合、正しくない挙動です。
なぜならば、MyObjectクラスの利用者は、次のように考えます。
・MyObjectのmyTextプロパティは、NSString型である。
・NSStringはImmutable(不変)である。
・myTextプロパティにセットされた値が、セッタ以外の操作によって変更されることはない。
しかし、実際には、プロパティにNSMutableStringオブジェクトをセットすることで、上記の憶測とは異なる挙動が引き起こされる可能性があるわけです。
この問題は、ただ単に、プロパティをretainからcopyに変更するだけで対処できます。
@interface MyObject : NSObject @property(copy) NSString* myText; @end @implementation MyObject @synthesize myText; @end int main(int argc, const char** argv) { @autoreleasepool { NSMutableString* s = [NSMutableString stringWithString:@"my name is pizza."]; MyObject* myObj = [[[MyObject alloc] init] autorelease]; myObj.myText = s; [s deleteCharactersInRange:NSMakeRange(11, 6)]; [s appendString:@"Linus Torvalds"]; NSLog(@"%@", s); //=>my name is Linus Torvalds. NSLog(@"%@", myObj.myText); //=>my name is pizza. } }
また、関連する話として、プロパティとして公開する値クラスは、Immutable(NSString, NSArray, ...)であるべきで、Mutable(NSMutableString, NSMutableArray, ...)にすべきではありません。Mutableなオブジェクトをcopyプロパティにするには、通常のプロパティ実装とは異なる対処が必要になります。
http://stackoverflow.com/questions/816720/whats-the-best-way-to-use-obj-c-2-0-properties-with-mutable-objects-such-as-ns
これは、クラスの不変性にも関わる問題です。
言語は異なりますが、考え方や、不変クラスの重要性については、Effective Java 第二版 「項目15 可変性を最小限にする」が参考になります。
でもcopyにすると遅くなるんでしょ?
Immutableなオブジェクトのcopyは、retainと同じ動作をします。
従って、遅くはなりません。
中身が変更されることがないと分かっていれば、copyで新しくオブジェクトを生成することは、必要のない防御策ということになります。retainとcopyが等価というのは、不変クラスの特性の1つと言えるでしょう。不変クラスの安全性については、Effective Java 第二版 「項目66 共有された可変データへのアクセスを同期する」でも言及されています。
Mutableなクラスのcopyは、retainよりも遥かに遅いでしょう。安全性よりも速度を重視せざるを得ない場面では、copyを避けることも必要かもしれません。しかし、Objective-Cでは、そのような速度重視の場面では、部分的にC言語を使うことができます。
やばく(遅く)なったらいつでもC, C++が使える。
この点が、Objective-Cの良いところだと私は感じています。
JNIなんて知らない。