2

私はCocoaのKVC/KVOとバインディングでいくつか風変わりな動作を見ています。私はNSArrayControllerオブジェクトを持っていて、その内容はNSMutableArrayに結び付けられていて、コントローラはarrangedObjectsというオブザーバとしてNSArrayControllerに登録されています。この設定では、配列が変更されるたびにKVO通知を受け取る予定です。ただし、KVO通知は一度だけ送信されるように見えます。配列が変更されたのは初めてです。KVC/KVOとバインディング:なぜ私は変更通知を1つだけ受け取っていますか?

Xcodeで新しい「Cocoa Application」プロジェクトを設定して問題を説明しました。ここに私のコードは次のとおりです。

BindingTesterAppDelegate.h

#import <Cocoa/Cocoa.h> 

@interface BindingTesterAppDelegate : NSObject <NSApplicationDelegate> 
{ 
    NSWindow * window; 
    NSArrayController * arrayController; 
    NSMutableArray * mutableArray; 
} 
@property (assign) IBOutlet NSWindow * window; 
@property (retain) NSArrayController * arrayController; 
@property (retain) NSMutableArray * mutableArray; 
- (void)changeArray:(id)sender; 
@end 

BindingTesterAppDelegate.m

#import "BindingTesterAppDelegate.h" 

@implementation BindingTesterAppDelegate 

@synthesize window; 
@synthesize arrayController; 
@synthesize mutableArray; 

- (void)applicationDidFinishLaunching:(NSNotification *)notification 
{ 
    NSLog(@"load"); 

    // create the array controller and the mutable array: 
    [self setArrayController:[[[NSArrayController alloc] init] autorelease]]; 
    [self setMutableArray:[NSMutableArray arrayWithCapacity:0]]; 

    // bind the arrayController to the array 
    [arrayController bind:@"content" // see update 
       toObject:self 
       withKeyPath:@"mutableArray" 
        options:0]; 

    // set up an observer for arrangedObjects 
    [arrayController addObserver:self 
         forKeyPath:@"arrangedObjects" 
         options:0 
         context:nil]; 

    // add a button to trigger events 
    NSButton * button = [[NSButton alloc] 
         initWithFrame:NSMakeRect(10, 10, 100, 30)]; 
    [[window contentView] addSubview:button]; 
    [button setTitle:@"change array"]; 
    [button setTarget:self]; 
    [button setAction:@selector(changeArray:)]; 
    [button release]; 

    NSLog(@"run"); 
} 

- (void)changeArray:(id)sender 
{ 
    // modify the array (being sure to post KVO notifications): 
    [self willChangeValueForKey:@"mutableArray"]; 
    [mutableArray addObject:[NSString stringWithString:@"something"]]; 
    NSLog(@"changed the array: count = %d", [mutableArray count]); 
    [self didChangeValueForKey:@"mutableArray"]; 
} 

- (void)observeValueForKeyPath:(NSString *)keyPath 
         ofObject:(id)object 
         change:(NSDictionary *)change 
         context:(void *)context 
{ 
    NSLog(@"%@ changed!", keyPath); 
} 

- (void)applicationWillTerminate:(NSNotification *)notification 
{ 
    NSLog(@"stop"); 
    [self setMutableArray:nil]; 
    [self setArrayController:nil]; 
    NSLog(@"done"); 
} 

@end 

そして、ここで出力されます:

load 
run 
changed the array: count = 1 
arrangedObjects changed! 
changed the array: count = 2 
changed the array: count = 3 
changed the array: count = 4 
changed the array: count = 5 
stop 
arrangedObjects changed! 
done 

あなたが見ることができるように、 KVO通知は初めて送信されます(アプリケーションが終了するともう一度送信されます)。なぜこれが当てはまるのでしょうか?

アップデート:私は私のNSArrayControllercontentArrayに結合する必要があることを指摘しorqueから

おかげだけではなく、そのcontent。上記掲載のコードは、すぐにこの変更が行われたとして、働く:

// bind the arrayController to the array 
[arrayController bind:@"contentArray" // <-- the change was made here 
      toObject:self 
      withKeyPath:@"mutableArray" 
       options:0]; 

答えて

7

まず、あなたはcontentArray(ないコンテンツ)に特異的に結合する必要があります。

[arrayController bind:@"contentArray" 
      toObject:self 
      withKeyPath:@"mutableArray" 
       options:0]; 

その後、簡単な方法は、単に使用することですあなただけ-addObject :)

を呼び出すためのボタンアクションをしたいそうよ

- (void)changeArray:(id)sender 
{ 
    // modify the array (being sure to post KVO notifications): 
    [arrayController addObject:@"something"]; 
    NSLog(@"changed the array: count = %d", [mutableArray count]); 
} 

(実際のシナリオで:arrayControllerは、配列を変更します

- [NSMutableArray addObject]を使用しても、コントローラに自動的には通知されません。 mutableArrayでwillChange/didChangeを手動で使用してこの問題を回避しようとしたことがわかります。配列自体は変更されていないため、これは機能しません。つまり、KVOシステムが変更前と変更後にmutableArrayを照会すると、同じアドレスを持つことになります。

使用したい場合は - [NSMutableArrayののaddObject]、あなたはwillChange/didChange arrangedObjectsにできます

- (void)changeArray:(id)sender 
{ 
    // modify the array (being sure to post KVO notifications): 
    [arrayController willChangeValueForKey:@"arrangedObjects"]; 
    [mutableArray addObject:@"something"]; 
    NSLog(@"changed the array: count = %d", [mutableArray count]); 
    [arrayController didChangeValueForKey:@"arrangedObjects"]; 
} 

同じ効果を与える安くキーがあるかもしれません。選択肢がある場合は、コントローラを操作して通知を基になるシステムに残すことをお勧めします。

+0

+1と非常に詳細な回答ありがとうございます。私は "content"ではなく "contentArray"へのバインディングを変更し、すべてが魅力的に機能しました。コントローラを介してアレイを変更する場合:これは単純化された例です。実際のアプリケーションでは、配列はモデルオブジェクトのプロパティであり、別のプロセスによって変更されます。 arrayControllerを使用してオブジェクトを変更する場合、Modelクラスをコントローラクラスに結合する必要があります。コントローラクラスはMVCパターンと完全に対応しています。 –

+0

ボタンのアクションは 'add:'であり、 'addObject:'ではなく、ボタンを追加します! –

5

全体値KVO通知を明示的に送信するよりも、array accessorsを実装して使用する方がはるかに良い方法です。その後、KVOは通知を無料で掲示します。

、これに代えてその方法:

[self willChangeValueForKey:@"things"]; 
[_things addObject:[NSString stringWithString:@"something"]]; 
[self didChangeValueForKey:@"things"]; 

あなたはこれを行うだろう:

[self insertObject:[NSString stringWithString:@"something"] inThingsAtIndex:[self countOfThings]]; 

だけでなく、KVOはあなたのための変更通知を掲載しますが、それは、より具体的な通知となり、アレイ全体の変更ではなくアレイの挿入による変更です。私ができるように

Iは、通常、上記しaddThingsObject:メソッドを追加:

[self addThingsObject:[NSString stringWithString:@"something"]]; 

add<Key>Object:ことは、現在の配列プロパティ(のみ設定特性)のためのKVC-認識セレクタ・フォーマットではなく、一方insertObject:in<Key>AtIndex:は、前者を実装しているので(これを行う場合)、を使用する必要があります。

+0

ありがとう!私は配列のアクセサーにいくつかの読書をしなければならないようだ。それは間違いなく私の綴りの理解の欠けている部分のように聞こえる。私はいくつかの 'NSMutableArray'プロパティを持つことができ、それぞれに独自の' insertObject:in AtIndex:メソッドが必要でしょうか? –

+0

私はサービスとして(ThisServiceを使用して)実行するスクリプトを用意しています。このスクリプトはivar宣言をとり、そのために便利なアクセサのほとんどを生成します。今日では、配列とセットのプロパティにのみ便利です。 http://boredzo.org/make-objc-accessors/ –

+0

したがって、ivar宣言(例えば、 "NSMutableArray * mutableArray;' ")をコピーしてヘッダーに貼り付け、貼り付けたものを選択し、Make Obj-Cアクセサ宣言サービスで、同じivar宣言を実装に貼り付け、貼り付けたものを選択し、Make Obj-Cアクセサ定義サービスを実行します。 –

0

ああ、私はこのソリューションのために長い時間を探していました!ありがとうございます ! は遊んアイデア&を取得した後、私は別の非常に派手な方法を発見した:

は、私がこのようなオブジェクトのCubeFramesがあるとします。私のアレイはCubeframesのオブジェクトが含まれてい

@interface CubeFrames : NSObject { 
NSInteger number; 
NSInteger loops; 
} 

、彼らは(MVC)を介して管理されていますobjectControllerによって作成され、tableViewに表示されます。 バインディングは一般的な方法で行われます。 objectControllerの "Content Array"が配列にバインドされています。重要 :今

-(void)awakeFromNib { 

// 
// register ovbserver for array changes : 
// the observer will observe each item of the array when it changes: 
//  + adding a cubFrames object 
//  + deleting a cubFrames object 
//  + changing values of loops or number in the tableview 
[dataArrayCtrl addObserver:self forKeyPath:@"arrangedObjects.loops" options:0 context:nil]; 
[dataArrayCtrl addObserver:self forKeyPath:@"arrangedObjects.number" options:0 context:nil]; 
} 

- (void)observeValueForKeyPath:(NSString *)keyPath 
        ofObject:(id)object 
        change:(NSDictionary *)change 
        context:(void *)context 
{ 
    NSLog(@"%@ changed!", keyPath); 
} 

、確かに、私はすべての変更をキャッチ:行の追加と削除、変更、私は私のAppdelegateでこのようなオブザーバーを追加する場合

クラスCubeFramesにobjectControllerの「クラス名」を設定しますループまたは番号:-)

関連する問題