サブクラスの対応【実践的Macintoshプログラミング解説】

印刷用表示 |テキストサイズ 小 |中 |大 |

CoreData版 Repeating Motif Generator の開発 Repeating Motif Wonderland CoreData 実践的 Macintosh プログラミング解説

LinkIconホーム

更新日 2009-05-24

リサージュ図形をカット&ペーストできる様にする

サブクラスの対応

 RMGRootに用意したメソッドをオーバーライドして、OscillatorCDとLissajousクラスをカット&ペーストに対応させます。

 OscillatorCDは単純にcopyKeysとstringDescriptionをオーバーライドすればよいのですが、LissajousはOscillatorCDを所有しているので深いコピーが必要です。したがってdictionaryRepresentationメソッドもオーバーライドする必要があります。

OscillatorCDの対応

OscillatorProperty2.png OscillatorCDをカット&ペーストに対応させるにはcopyKeysとstringDescriptionをオーバーライドしておけば充分です。

 copyKeysの実装は、前述のAppleのドキュメントに倣ってスタティック変数に配列をキャッシュしてそれを返します。Objective-Cにはクラス変数がないので、その代わりにスタティック変数を使います。

 配列の中身は三つのキー文字列amplitude, frequency, phaseLagで、これはOscillatorCDの三つの属性の名前です。

 lissajous関連はoscillators関連の逆関連なので、OscillatorCDクラスのインスタンスをoscillators関連に追加した時に自動的にセットされます。したがってオブジェクトの辞書表現に含める必要はありません。

 stringDescriptionは三つの属性値をある書式にあてはめて文字列に展開したものを返しています。

//
//  OscillatorCD.m
//

#pragma mark -
#pragma mark PasteBoard methods

+ (NSArray *)copyKeys
{
    static NSArray *oscillatorCopyKeys = nil;

    if(oscillatorCopyKeys == nil)
        oscillatorCopyKeys = [[NSArray alloc] initWithObjects:@"amplitude", @"frequency", @"phaseLag", nil];

    return  oscillatorCopyKeys;
}

- (NSString *)stringDescription
{
    return  [NSString stringWithFormat:@"amplitude:%d, frequency:%d, phaseLag:%d",
             [[self amplitude] intValue],[[self frequency] intValue],[[self phaseLag] intValue]];
}

Lissajousの対応

Oscillatorを所有するので、異なる対応が必要

LissajousProperty.png Lissajousをカット&ペーストに対応させるにはcopyKeysとstringDescriptionをオーバーライドしただけでは不十分で、dictionaryRepresentationもオーバーライドする必要があります。

 RMGRootのdictionaryRepresentationメソッドがcopyKeysを使ってオブジェクトの辞書表現を作る事ができるのは、対象が属性の場合だけだからです。

 関連に設定されているOscillatorの情報が抜け落ちてしまっていては、ペーストした時に元のリサージュ図形になりません。

 またOscillatorは単独でカット&ペーストしないのでdataTypeメソッドを実装する必要はありませんがLissajousは必要です。

Oscillatorに辞書表現を作ってもらう

 対象が関連という事は、つまり相手にするのが管理対象オブジェクトだということです。単純な属性と違って、管理対象オブジェクト自体を復元できるような情報を一つの値で表現する事はできません。

 では、どうするか?そのような情報は、その管理対象オブジェクトに作ってもらえばよいのです。
 ここでは各OscillatorにdictionaryRepresentationメッセージを送ってオブジェクトの辞書表現を返してもらい、それをLissajousの辞書表現に含めるという方法をとっています。

Lissajousの辞書表現からLissajousを復元する

 ペーストする時はLissajousクラスのインスタンスにsetValuesForKeysWithDictionary:メッセージが送られます。引数はLissajousのdictionaryRepresentationメソッドが返した辞書です。

 すると引数の辞書のキー - 値ペアを使ってsetValue:forKey:メッセージが送られます。よってキーに対応するアクセサメソッドがあれば、それを使って値が設定されます。

 Lissajousの辞書表現にはresolution, xoscillator, yoscillatorという三つのキーが入っています。resolutionはアクセサメソッドが既に用意されていますが、xoscillator, yoscillatorはdictionaryRepresentationメソッドで今追加したものなので、アクセサメソッドがありません。そこでこれを追加します。

 setXoscillator:メソッドの引数は、LissajousのdictionaryRepresentationメソッドが返した辞書の中にxoscillatorキーの値として入っているオブジェクトです。これはつまりOscillatorにdictionaryRepresentationメッセージを送って返ってきた辞書という事になります。したがって、この辞書を引数としてOscillatorにsetValuesForKeysWithDictionary:メッセージを送れば、Oscillatorの内容が復元されるという事になります。

ソースコードの方が簡潔

 以上、言葉で長々と説明してきましたが、これをコードで表現すると以下の様に簡潔なものになります。

//
//  Lissajous.m
//

#pragma mark -
#pragma mark PasteBoard methods

+ (NSString *)dataType
{
    static NSString *lissajousDataType = nil;

    if(lissajousDataType == nil)
        lissajousDataType = [[NSString alloc] initWithString:@"RMGLissajousPBoardType"];

    return  lissajousDataType;
}

+ (NSArray *)copyKeys
{
    static NSArray *lissajousCopyKeys = nil;

    if(lissajousCopyKeys == nil)
        lissajousCopyKeys = [[NSArray alloc] initWithObjects:@"resolution", nil];

    return  lissajousCopyKeys;
}

- (NSString *)stringDescription
{
    return  [NSString stringWithFormat:@"resolution:%d, %@, %@",
             [[self resolution] intValue],[[self x] stringDescription],[[self y] stringDescription]];
}

- (NSDictionary *)dictionaryRepresentation
{
    NSMutableDictionary *dictionary;

    dictionary = [[[super dictionaryRepresentation] mutableCopy] autorelease];
    [dictionary setObject:[[self x] dictionaryRepresentation] forKey:@"xoscillator"];
    [dictionary setObject:[[self y] dictionaryRepresentation] forKey:@"yoscillator"];

    return  dictionary;
}

- (OscillatorCD *)xoscillator   {return  [self x];}
- (OscillatorCD *)yoscillator   {return  [self y];}

- (void)setXoscillator:(NSDictionary *)dictionary   {[[self x] setValuesForKeysWithDictionary:dictionary];}
- (void)setYoscillator:(NSDictionary *)dictionary   {[[self y] setValuesForKeysWithDictionary:dictionary];}

【失敗例】Setterだけでは半人前?


 最初、xoscillatorとyoscillatorのアクセサメソッドはsetXoscillator:とsetYoscillator:しか実装していませんでした。Getterを使う機会がないので不要だと思ったからです。

 ところが実行してみると、リサージュ図形をペーストした時に発振器の値がデフォルトのままになってしまう不具合が発生しました。コンソールを見てみると、以下のメッセージが表示されています。

[<Lissajous 0x19cbd0> valueForUndefinedKey:]: the entity Lissajous is not key value coding-compliant for the key xoscillator.
The Debugger has exited with status 0.


 Lissajousはxoscillatorキーに対してkey value codingに適合していないと見なされてしまった様です。これはxoscillatorとyoscillatorメソッドを実装すると解決しますので、GetterとSetterが両方そろって初めて一人前(KVC-compliant)という事なんですね。