|
|
Log In | Not a Member? |
Contact ADC |
| < Previous PageNext Page > |
Cocoaオブジェクトは可変または不変のどちらかです。不変オブジェクトのカプセル化された値を変更することはできません。そのようなオブジェクトを作成した場合、オブジェクトが表す値はオブジェクトの寿命全体を通じて同じに保たれます。しかし、可変オブジェクトのカプセル化された値は、いつでも変更できます。以降の各セクションでは、オブジェクト型に可変および不変のバリエーションが存在する理由を説明します。また、オブジェクトの可変性が持つ特性と間接的な影響を示し、可変性が問題となる場合にオブジェクトを扱う最適な方法を提示します。
オブジェクトに可変および不変のバリエーションが存在する理由
可変オブジェクトを使用したプログラミング
オブジェクトは、デフォルトでは可変です。ほとんどのオブジェクトでは、カプセル化されたデータを“setter”(設定)アクセサメソッドを使用して変更できます。たとえば、NSWindowオブジェクトのサイズ、位置、タイトル、バッファ動作その他の特性は変更可能です。適切に設計されたモデルオブジェクト(たとえば、顧客レコードを表すオブジェクトなど)では、インスタンスデータを変更するためにsetterメソッドが必要になります。
Foundationフレームワークでは、可変および不変のバリエーションを持つクラスを導入することによって、この全体像に若干の色が加わります。可変サブクラスは、通常は不変スーパークラスのサブクラスであり、クラス名に“Mutable”という文字列を含みます。このようなクラスには次のものがあります。
NSMutableArray
NSMutableDictionary
NSMutableSet
NSMutableIndexSet
NSMutableCharacterSet
NSMutableData
NSMutableString
NSMutableAttributedString
NSMutableURLRequest
注: Application KitのNSMutableParagraphStyleを除いて、Foundationフレームワークでは現在、明示的に名前が付けられている可変クラスがすべて定義されています。ただし、Cocoaのどのフレームワークも、独自にクラスの可変および不変のバリエーションを持つことが可能です。
これらのクラスの名前は特殊ですが、対応する不変クラスよりも可変クラスの標準的な名前に近いものになっています。なぜこのように複雑なのでしょうか。可変オブジェクトが不変のバリエーションを持つことにはどのような役割があるのでしょうか。
すべてのオブジェクトが可変である場合を考えてみましょう。アプリケーションの中でメソッドを呼び出し、文字列を表すオブジェクトへの参照を取得します。この文字列をユーザインターフェイスの中で特定のデータを識別するために使用します。ここで、アプリケーション内の別のサブシステムが、同じ文字列への独自の参照を取得し、それを変異させたとします。突然、自分の管理の及ばないところでアプリケーションのラベルが変更されました。さらに深刻な事態も考えられます。たとえば、テーブルビューへのデータの設定に使用する配列への参照を取得したとします。配列内のオブジェクトに対応する行をユーザが選択したとき、そのオブジェクトがプログラムのどこかのコードによってすでに削除されていたら、問題になります。不変性とは、オブジェクトを使用している間、その値が予期しないところで変更されないことの保証です。
不変性の候補に適しているオブジェクトは、個別の値で構成されるコレクションをカプセル化したオブジェクトや、バッファ(これら自身も、文字またはバイトのコレクションの一種です)に格納された値を含んでいるオブジェクトです。しかし、必ずしも“値”オブジェクトがすべて可変のバリエーションの恩恵を受けるわけではありません。NSNumberやNSDateのインスタンスなど、単純な値を1つだけ持つオブジェクトは、可変性のよい候補ではありません。このようなオブジェクトでは、オブジェクトが表す値が変わる場合には、古いインスタンスを新しいインスタンスで置き換えるほうが意味があります。
文字列や辞書などを表すオブジェクトでは、パフォーマンス上の理由から、不変のバリエーションを使用します。これらのオブジェクトの場合、その可変オブジェクトには多少のオーバーヘッドが伴います。可変オブジェクトでは、必要に応じてメモリ領域の割り当ておよび割り当て解除を行うなど、変更が可能なバッキングストアを動的に管理しなければならないため、不変オブジェクトよりも効率が悪い可能性があります。
理論上は不変性によってオブジェクトの値が不変であることが保証されますが、現実にはこのことが常に保証されるわけではありません。メソッドが不変のバリエーションを、可変オブジェクト型の戻り値として返すことがあります。後に、そのメソッドがオブジェクトを変異させて、受け取り側が先に受け取った値に基づいて行った想定や選択に反する可能性があります。また、さまざまな変換処理の過程でオブジェクトそのものの可変性が変わることもあります。たとえば、NSPropertyListSerializationクラスを使用してプロパティリストをシリアライズすると、オブジェクトの可変性の性質は維持されず、辞書、配列などその一般的な種類だけが維持されます。そのため、このプロパティリストのシリアライズを解除して得られるオブジェクトは、元のオブジェクトと同じクラスにならないことがあります。たとえば、NSMutableDictionaryオブジェクトであったものがNSDictionaryオブジェクトになってしまうことがあります。
オブジェクトの可変性が問題になる場合は、何らかの防御的なプログラミング手法を採用するのが最善です。いくつかの一般的な経験則を次に示します。
オブジェクトの作成後にその内容を頻繁にまたは部分的に少しずつ変更する必要がある場合は、オブジェクトの可変のバリエーションを使用する。
ある不変オブジェクトを別の不変オブジェクトに置き換えたほうがよい場合がある。たとえば、文字列値を保持するインスタンス変数のほとんどは、不変のNSStringオブジェクトに割り当てて、“setter”メソッドを使用して置き換えるようにしたほうがよい。
可変かどうかについては、戻り型を利用する。
オブジェクトが可変かどうか、または可変であるべきかどうか判断できない場合には、不変にする。
このセクションでは上記の経験則でわかりにくい部分について検討し、可変オブジェクトを使用してプログラミングを行う際の典型的な選択肢について説明します。また、Foundationフレームワークが備えている、可変オブジェクトを作成するためのメソッドと、可変および不変のオブジェクトのバリエーション間で変換を行うメソッドについて、それらの概要を示します。
可変オブジェクトを作成するには、標準のネストされたalloc-initメッセージを使用します。たとえば次のようにします。
NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] init]; |
ただし、可変クラスの多くは、オブジェクトの初期容量または見込み容量を指定できるイニシャライザやファクトリメソッドを備えています。たとえば、NSMutableArrayは次に示すarrayWithCapacity:クラスメソッドを備えています。
NSMutableArray *mutArray = [NSMutableArray arrayWithCapacity:[timeZones count]]; |
このような手がかりを与えることによって、可変オブジェクトのデータの格納効率が高まります(クラスファクトリメソッドでは自動解放されるインスタンスを返すことが規則になっているため、コードの中でオブジェクトを存続させる必要がある場合は必ずオブジェクトを保持してください)。
また、既存のオブジェクトについて、その汎用型の可変コピーを作成することによって可変オブジェクトを作成することもできます。それには、Foundation可変クラスのそれぞれの不変スーパークラスに実装されているmutableCopyメソッドを呼び出します。
NSMutableSet *mutSet = [aSet mutableCopy]; |
逆に、可変オブジェクトにcopyを送ってその不変コピーを作成できます。
不変および可変のバリエーションを持つFoundationクラスの多くは、それらのバリエーション間の変換を行う次のようなメソッドを備えています。
type With Type :―arrayWithArray:など
set Type :―setString:など(可変クラスのみ)
initWith Type :copyItems:―initWithDictionary:copyItems:など
Cocoaの開発では、インスタンス変数を可変にするか不変にするかを決めなければならないことがよくあります。辞書や文字列など、値が変更される可能性のあるインスタンス変数では、どのような場合にオブジェクトを可変にするのが適切なのでしょうか。また、オブジェクトを不変にして、オブジェクトが表す値がかわったときに別のオブジェクトに置き換えるようにしたほうがよいのは、どのような場合でしょうか。
一般に、オブジェクトの内容がまるごと変わる場合には、不変オブジェクトを使用するのが適切です。文字列(NSString)やデータオブジェクト(NSData)は、通常はこの分類に該当します。オブジェクトが部分的に少しずつ変わる可能性が高い場合は、オブジェクトを可変にするのが理にかなっています。配列や辞書などのコレクションがこの分類に該当します。ただし、その際にコレクションの変更頻度やサイズを考慮します。たとえば、ほとんど変化しない小さな配列を使用する場合は、不変にするのが適切です。
このほか、インスタンス変数として持たれるコレクションの可変性を決めるときは、次を考慮します。
頻繁に変更があり、クライアントに渡す(つまり、“getter”(取得)アクセサメソッドを通じて直接返す)ことが多い可変コレクションの場合、クライアント側に参照があるかもしれない情報を変えてしまう危険がある。この危険性が高い場合は、インスタンス変数を不変にする。
インスタンス変数の値が頻繁に変わるものの、getterメソッドを通じてクライアントに返すことがほとんどない場合は、インスタンス変数を可変にできる。ただし、アクセサメソッドは、自動解放される不変コピーを返すようにする(例を、Listing 2-13に示す)。
Listing 2-13 可変インスタンス変数の不変コピーを返す
@interface MyClass :NSObject { |
// ... |
NSMutableSet *widgets; |
} |
// ... |
@end |
@implementation MyClass |
- (NSSet *)widgets { |
return (NSSet *)[[widgets copy] autorelease]; |
} |
クライアントに返す可変コレクションを扱う場合の高度な手法の1つとして、オブジェクトが現在可変か不変かを記録するフラグを持つという方法があります。変更が発生した場合は、オブジェクトを可変にして変更を適用します。コレクションを返すときは、オブジェクトを(必要に応じて)不変にしてから返します。
メソッドの呼び出し側では、次の2つの理由から、返されるオブジェクトの可変性に関心があります。
オブジェクトの値を変更できるかどうかを知る必要がある。
オブジェクトへの参照を持っている間に、オブジェクトの値が予期しないところで変わるかどうかを知る必要がある。
レシーバは、受け取ったオブジェクトを変更できるかどうかを判断するために、戻り値の型を利用する必要があります。たとえば、不変として型定義された配列オブジェクトを受け取った場合は、それを変えようとしてはなりません。次の例のように、オブジェクトの所属クラスに基づいてオブジェクトが可変かどうかを判断するのは、適切なプログラミング手法とは言えません。
if ( [anArray isKindOfClass:[NSMutableArray class]] ) { |
// anArrayに対してオブジェクトを追加または削除する |
} |
実装上の理由によって、この例のisKindOfClass:からは正確な情報が返されない可能性があります。しかし、これ以外の理由からも、所属クラスに基づいてオブジェクトが可変かどうかを判断するべきではありません。オブジェクトが可変かどうかは、オブジェクトを返すメソッドのシグネチャが示す可変性の情報にのみ基づいて判断してください。オブジェクトが可変か不変かがわからない場合は、不変であると仮定してください。
いくつかの例を示します。これらの例を考えれば、このようなガイドラインが重要である理由が明らかになると思います。
ファイルからプロパティリストを読み取ります。Foundationフレームワークによってこのリストが処理される際に、プロパティリストのさまざまなサブセットどうしが同一であることが認識され、それらすべてのサブセットの間で共有する一連のオブジェクトが作成されます。その後、作成されたプロパティリストオブジェクトを調べ、あるサブセットを変えることにしたとします。すると、気が付かないうちにツリーの複数の場所を変えてしまうことになります。
NSViewに対してそのサブビューを問い合わせます(subviewsメソッド)。NSArrayとして宣言されたオブジェクトが返されますが、このオブジェクトは内部的にはNSMutableArrayである可能性があります。次に、この配列をほかの何らかのコードに渡します。そのコードでは、イントロスペクションを使用して配列が可変であると判断し、配列を変更します。配列を変更したことによって、そのコードはNSViewの内部的なデータ構造を変えることになります。
したがって、オブジェクトの可変性を、イントロスペクションを通じて得られるオブジェクトの情報に基づいて判断しないようにします。オブジェクトを可変として扱うか不変として扱うかは、APIの境界(つまり、戻り型)に基づいて判断するようにします。オブジェクトをクライアントに渡すときに可変または不変であることをはっきりと示す必要がある場合は、その情報を示すフラグをオブジェクトと一緒に渡します。
不変と考えられるオブジェクトをメソッドから受け取った際に、そのオブジェクトが自分の知らないところで変わらないようにしたい場合は、オブジェクトをローカルにコピーすることでオブジェクトの“スナップショット”を作成できます。そして、必要に応じて、保存しておいたそのオブジェクトを最新のバージョンと比較します。オブジェクトが変わっていたら、保存しておいたバージョンのオブジェクトに依存していたプログラム内のものをすべて調整できます。Listing 2-14に、この手法の実装例を示します。
Listing 2-14 可変と考えられるオブジェクトのスナップショットの作成
static NSArray *snapshot = nil; |
- (void)myFunction { |
NSArray *thingArray = [otherObj things]; |
if (snapshot) { |
if ( ![thingArray isEqualToArray:snapshot] ) { |
[self updateStateWith:thingArray]; |
} |
} |
snapshot = [thingArray copy]; |
} |
オブジェクトのスナップショットを作成して後で比較するという方法には、負担が大きいという問題があります。同じオブジェクトについて複数のコピーを作成する必要があります。それよりも効率的な方法としてキー値監視を使用する方法があります。このプロトコルの詳細については、“「キー値監視について」”を参照してください。
可変オブジェクトをコレクションオブジェクトに格納すると、問題が生じることがあります。コレクションに含まれているオブジェクトが変わると、コレクションが無効になったり、場合によっては壊れたりすることがあります。これは、オブジェクトが変わることでコレクションにおけるオブジェクトの格納方法が影響を受ける可能性があるからです。第一に、NSDictionaryオブジェクトやNSSetオブジェクトなどのハッシュコレクションのキーとなっているオブジェクトのプロパティが変更されると、そのプロパティによってオブジェクトのhashメソッドやisEqual:メソッドの結果が影響を受ける場合、コレクションが壊れます(コレクション内のオブジェクトのhashメソッドが内部状態を利用していなければ、コレクションが壊れる可能性は低くなります)。第二に、ソートされた配列などの順序付きコレクションのオブジェクトのプロパティが変えられた場合に、配列内のオブジェクトどうしを比較する方法が影響を受け、そのために順序が無効になる可能性があります。
| < Previous PageNext Page > |
Last updated: 2006-05-23
|
Get information on Apple products.
Visit the Apple Store online or at retail locations. 1-800-MY-APPLE Copyright © 2007 Apple Inc. All rights reserved. | Terms of use | Privacy Notice |