初期化処理を追加する【実践的Macintoshプログラミング解説】

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

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

LinkIconホーム

更新日 2009-05-24

モデルを作る

初期化処理を追加する

 属性で初期化が必要なものがあるので、その処理を追加します。

 特定のOscillatorへのアクセサメソッドですが、変に凝った事をやって予期しないエラーに見舞われてしまいました。エンティティに関連を追加して、初期化時にその関連にオブジェクトを設定しておくという素直な事をやっておいたほうがよさそうです。

awakeFromInsertで初期化を実行

 awakeFromInsertでやるべき事は、Oscillatorを作成し、それをoscillators対多関連に登録する事とx, y関連にセットする事です。よってLissajousの初期化コードは以下の様になります。

 重要事項としてデベロッパドキュメントに載っていますが、サブクラスは自分の初期化コードを実行する前にスーパークラスのawakeFromInsertを呼び出さないといけません。これはawakeFromFetchでも同じです。 

// 
//  Lissajous.m
//

#pragma mark -
#pragma mark Initializer

- (void)createOscillator
{
    int i;
    OscillatorCD    *osc;

    for(i=0;i<2;i++)
    {
        osc = [NSEntityDescription insertNewObjectForEntityForName:@"Oscillator"
                                            inManagedObjectContext:[self managedObjectContext]];
        [[self mutableSetValueForKey:@"oscillators"] addObject:osc];
        if(i==0)
            [self setX:osc];
        else
            [self setY:osc];
    }
}

- (void)awakeFromInsert
{
    [super awakeFromInsert];
    [self createOscillator];
    [self updateBezierPath];
}

 

ファイルをオープンしただけでダーティになる

 第一のプロジェクトで作成したアプリケーションでは問題なかったのに、第二のプロジェクトで作成したアプリケーションはファイルを開いただけでドキュメントがダーティになる問題が発生しました。ファイルを開いて、少しでもマウスカーソルを動かすとダーティになります。
 このときアンドゥメニューが有効になっており、アンドゥを実行するとリサージュ図形が消えます。という事はLissajousのbezierPath属性をセットする部分がアンドゥの対象になっている様です。

 OscillatorCDのawakeFromFetchでupdateBezierPathを呼び出しており、OscillatorCDのupdateBezierPathがLissajousのupdateBezierPathを呼び出しています。試みにLissajousのupdateBezierPathを呼び出す部分をコメントアウトしたらダーティにならなくなりました。

 ドキュメントのawakeFromFetchにはこう書いてあります。

This method is commonly used to compute derived values or to recreate transient relationships from the receiver’s persistent properties.

The change processing is explicitly disabled around this method so that you can conveniently use public setters to establish transient values and other caches without dirtying the object or its context. Because of this, however, you should not modify relationships in this method as the inverse will not be set.

 オブジェクトやコンテキストをダーティにしないで値を変更できて便利ですよと書いてあり、実際第一のプロジェクトではその通りでした。でも今はそうなっていません。
 動作から類推すると、OscillatorCDのawakeFromFetchでダーティになる事を抑制してくれるのは、自分自身の属性に限定される様です。他のオブジェクトの属性を変更した場合、それを抑制してくれる事はありません(つまりダーティになる)。

 であれば解決策は簡単で、明示的にアンドゥの登録を禁止すればよいわけです。OscillatorCDのawakeFromFetchを以下の様に修正します。

// 
//  OscillatorCD.m
//

- (void)awakeFromFetch
{
    [super awakeFromFetch];
    [UNDO_MANAGER disableUndoRegistration];
    [self commonAwake];
    [UNDO_MANAGER enableUndoRegistration];
}

【失敗例】特定のOscillatorへのアクセサメソッドに関する試行錯誤

 リサージュ図形はX軸の発振器とY軸の発振器の出力を組み合わせて作られます。X軸の発振器のパラメータを設定したいといったように特定のOscillatorにアクセスしたい場合が考えられるので、特定のOscillatorへのアクセサメソッドが必要です。

 最終的にx, yという関連を追加する事で落ち着いたのですが、最初は別のアプローチを試みて失敗しました。

第一の失敗〜対多関連のOscillatorを全数チェックする

 最初は関連を追加せず、アクセサメソッドだけを用意しました。
 検索対象が二つしかないので、全部見て該当するものを返せば済むと考えました。NSEnumeratorを使ってループを回し、orderが0ならX軸の発振器、1ならY軸の発振器というわけです。order属性はOscillatorCDクラスのオブジェクトを生成する時に設定しておきます。

 特定のOscillatorへのアクセサメソッドx, yは以下のコードとしました。

// 
//  Lissajous.m
//

- (OscillatorCD *)oscillatorN:(int)n
{
    NSSet           *oscillators;
    NSEnumerator    *enumerator;
    OscillatorCD    *oscillator;

    oscillators = [self mutableSetValueForKey:@"oscillators"];
    enumerator = [oscillators objectEnumerator];
    while(oscillator = [enumerator nextObject])
        if([[oscillator order] intValue] == n)
            return  oscillator;

    return  nil;
}

- (OscillatorCD *)x         {return  [self oscillatorN:0];}
- (OscillatorCD *)y         {return  [self oscillatorN:1];}

 orderはこの様に設定します。

// 
//  Lissajous.m
//

- (void)createOscillator
{
    int i;
    OscillatorCD    *osc;

    for(i=0;i<2;i++)
    {
        osc = [NSEntityDescription insertNewObjectForEntityForName:@"Oscillator"
                                            inManagedObjectContext:[self managedObjectContext]];
        [osc setOrder:[NSNumber numberWithInt:i]]; 
        [[self mutableSetValueForKey:@"oscillators"] addObject:osc];
    }
}

 こうするとAddボタンを押してLissajousオブジェクトを生成した後にundoするとコンソールに以下のメッセージが表示されます。内部で何が起こっているのかよくわかりませんがKey Value Observingの処理が破綻している様です。「変更メッセージが届いたんだけど、監視対象のオブジェクトが消えちゃったよ!」といった感じでしょうか?
 OscillatorCDクラスのオブジェクトを返してもダメで、それを監視するKVO準拠のオブジェクト(例えばNSKeyValueNotifyingMutableSetの様な)が要求されている様にも思えますが、これ以上の事は私にはわかりません。

resolution: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: frequency
Observed object: <OscillatorCD: 0x1b72c0> (entity: Oscillator; id: 0x1b7310 <x-coredata:///Oscillator/t67A10AA3-CEBE-435C-8B54-F96AE73FDD304> ; data: {
    amplitude = nil;
    bezierPath = nil;
    frequency = nil;
    lissajous = nil;
    order = nil;
    phaseLag = nil;
})
Change: {
    kind = 1;
}
Context: 0x1b57a0

第二の失敗例〜x, yセットの方法

 アクセサメソッドを用意して、自分でオブジェクトを探し出して返してもうまくいきませんでした。CoreDataにはCoreDataの作法があるという事でしょう。Lissajousエンティティにxとyという関連を追加して、それにオブジェクトをセットすればこのような問題は起きません…起きない筈でした。

 別の問題にひっかかって初期化コードに問題があるのではないか?と疑い、初期化時にxとyをセットするのではなくアクセサメソッドで初期化の有無を判断して必要なら初期化するようにした事があります。このようなコードです。

// 
//  Lissajous.m
//

- (void)fixXY
{
    NSArray *array;

    array = [[self mutableSetValueForKey:@"oscillators"] allObjects];
    [self setX:[array objectAtIndex:0]];
    [self setY:[array objectAtIndex:1]];
}

- (OscillatorCD *)x
{
    OscillatorCD    *osc = VFK("x");
    if(osc == nil)
    {
        [self fixXY];
        osc = VFK("x");
    }

    return  osc;
}

- (OscillatorCD *)y
{
    OscillatorCD    *osc = VFK("y");
    if(osc == nil)
    {
        [self fixXY];
        osc = VFK("y");
    }

    return  osc;
}

 こうするとAddボタンを押してLissajousオブジェクトを生成した後にundoするとクラッシュしてしまいます。コンソールに何らかのメッセージが表示されるわけでもなく、いきなりデバッガに落ちます。

 このような試行錯誤を経て、結局OscillatorCDクラスのオブジェクトを生成する時に、x, y関連にこのオブジェクトを設定するのが一番という結論に落ち着きました。これだと今のところ問題は発生していません。