2

Cocoa の KVC/KVO とバインディングで奇妙な動作が見られます。「コンテンツ」が にバインドされたNSArrayControllerオブジェクトがあり、 のプロパティNSMutableArrayのオブザーバーとしてコントローラーが登録されています。このセットアップでは、アレイが変更されるたびに KVO 通知を受け取ることを期待しています。ただし、KVO 通知は 1 回しか送信されないようです。配列が初めて変更されたとき。arrangedObjectsNSArrayController

問題を説明するために、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 通知は初回のみ (およびアプリケーションの終了時にもう一度) 送信されます。なぜこれが当てはまるのでしょうか?

アップデート:

その だけでなく、 myの にバインドする必要があることを指摘してくれたorqueに感謝します。この変更が行われるとすぐに、上記の投稿されたコードが機能します。contentArrayNSArrayControllercontent

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

3 に答える 3

7

まず、contentArray(コンテンツではなく)にバインドする必要があります。

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

次に、簡単な方法は、arrayControllerを使用して配列を変更することです。

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

(実際のシナリオでは、ボタンアクションで-addObjectを呼び出したいだけです:)

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

-[NSMutableArray addObject]を使用する場合は、arrangedObjectsでwillChange/didChangeを実行できます。

- (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"];
}

同じ効果をもたらすより安価なキーがあるかもしれません。選択肢がある場合は、コントローラーを介して作業し、通知を基盤となるシステムに任せることをお勧めします。

于 2009-08-21T20:51:55.593 に答える
5

値全体の KVO 通知を明示的に投稿するよりもはるかに優れた方法は、配列アクセサーを実装して使用することです。その後、KVO は通知を無料で投稿します。

そのように、これの代わりに:

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

次のようにします。

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

KVO は変更通知を投稿するだけでなく、アレイ全体の変更ではなくアレイ挿入の変更という、より具体的な通知になります。

私は通常addThingsObject:、上記を実行するメソッドを追加して、次のことができるようにします。

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

add<Key>Object:は現在、配列プロパティ (設定プロパティのみ) の KVC 認識セレクタ形式ではありませんがinsertObject:in<Key>AtIndex:、そうであるため、前者の実装 (それを選択した場合)は後者を使用する必要があります。

于 2009-08-21T23:17:03.223 に答える
0

ああ、私はこの解決策を長い間探していました! ありがとうございます !アイデアを得て遊んだ後、私は別の非常に素晴らしい方法を見つけました:

次のようなオブジェクト CubeFrames があるとします。

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

私の配列にはCubeframesのオブジェクトが含まれており、objectControllerによって(MVC)を介して管理され、tableViewに表示されます。バインドは一般的な方法で行われます。objectController の「Content Array」が自分の配列にバインドされます。重要: objectController の「クラス名」をクラス CubeFrames に設定します。

Appdelegate に次のようなオブザーバーを追加すると:

-(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);
}

今、確かに、私はすべての変更をキャッチします:行の追加と削除、ループまたは番号の変更:-)

于 2016-02-02T11:23:56.783 に答える