独自クラスをディープコピーに対応させる : Objective-C プログラミング
PROGRAM
独自クラスをディープコピーに対応させる
Mac OS X や iPhone プログラミングで、NSString や NSArray, NSDictionary といった一般的なクラスには copy メソッドが実装されていて、インスタンスの複製を行うことが出来るようになっています。
もっとも、NSDictionary や NSArray といった copy メソッドでは、"シャローコピー" といって、保持しているインスタンスを retain で複製する方式がとられるようなので、コピー後にもとになった配列の要素値を変更したりするとコピー先も変更されたりするため、コピーの感覚が思っているものと少し違うかもしれません。
一般的な感覚では、copy というのはすなわち、各インスタンスの複製をとる "ディープコピー" が自然な挙動に思えます。
コピーの際の挙動はともかくとして、独自に実装したクラスでも NSCopying インターフェイスを継承することで、他のクラスと統一されたインターフェイスを用いた copy メソッドを提供することが可能になります。それがシャローコピーになるかディープコピーになるかは、ここの実装次第なので、そういった意味でも大切な実装なのではないかと思います。
また、NSArray や NSDictionary には、それぞれ initWithArray:copyItems: や initWithDictionary:copyItems: という引数に渡されたインスタンスを複製する形で初期化を行うメソッドがあって、ここの copyItems で YES を指定することでディープコピーを行うことが出来るようになっているのですけど、この時にも、今回のお話で登場する NSCopying インターフェイスで実装した copy メソッドが利用されます。
ですので、これを実装することで、コーディング自体もとても柔軟で見通しの良いものになると思います。
NSCopying を実装する
例えば、CopyingClass というクラスを実装するとします。
このとき、copy メソッドを実装するためには、インターフェイスの定義で NSCopying インターフェイスを継承する必要があります。
// NSCopying を継承した CopyingClass を定義します。
@interface CopyingClass : NSObject <NSCopying>
{
NSString* stringValue;
NSArray* arrayValues;
}
上記では併せて、値を保持するための属性を 2 つ宣言しています。
この属性値を copy メソッドを使ってディープコピーで複製できるようにするために、NSCopying で定義されている copyWithZone: メソッドを実装します。
// copy メソッドを実装するために、copyWithZone: メソッドを実装しています。
- (id)copyWithZone:(NSZone*)zone
{
// 複製を保存するためのインスタンスを生成します。
CopyingClass* result = [[[self class] allocWithZone:zone] init];
if (result)
{
[result->stringValue release];
[result->arrayValues release];
result->stringValue = [stringValue copyWithZone:zone];
result->arrayValues = [[NSArray allocWithZone:zone] initWithArray:arrayValues copyItems:YES];
}
return result;
}
正しくディープコピーされるように注意して実装します。
上記の例では、予め通常の方法で初期化されたインスタンスを、渡された zone 内に確保しています。その上で、初期化された属性値をいったん解放し、それぞれの属性のディープコピーを確保したインスタンスに設定するという方法をとっています。
もっとスマートな方法、たとえば initWithString:array: といった値の設定を同時に行う初期化メソッドを用意するなど、検討の余地はあるとは思いますが、ともあれこのような感じで実装して行くことになります。
また、インスタンスを生成する際に [self class] を利用していますが、これは、クラスを派生したときに、派生先でも copy メソッドを実装する際に都合が良いようなのでこうしています。
こうすることで、派生先から親の copyWithZone を呼び出したときでも、派生先のインスタンスが生成されて、そのうちの基底クラスに実装されている属性の複製がとられたインスタンスが返されるので、派生先では自分自身に実装されている属性の複製に専念することができます。
この copyWithZone: メソッドが実装されれば、これにより copy メソッドが利用できるようになります。
copy メソッドは内部で copyWithZone: を呼び出しているようですので、この実装をしていないと、copy メソッド実行時に、copyWithZone: が実装されていないというランタイムエラーが発生します。
copy メソッドが実装できれば、あとは NSArray や NSDictionary でディープコピーを実行すれば、値として格納されているインスタンスの copy メソッドが自動で呼び出されるので、これで独自クラスの扱いがとてもしやすくなると思います。
余談になりますが、クラスによっては copy: という、自身のインスタンスに引数で渡されたインスタンスの値を複製する機能を持ったメソッドが実装されている場合があります。例えば、"[newValue copy:sourceValue]" といった使い方のメソッドです。
これと今回のお話とは無関係なので、そういったメソッドも利用したい場合には自分で実装する必要があります。
[ もどる ]