2

我看到一些古怪的行爲與可可的KVC/KVO和綁定。我有一個NSArrayController對象,其「內容」綁定到NSMutableArray,並且我有一個控制器在NSArrayController上註冊爲arrangedObjects屬性的觀察者。使用此設置,我希望每次修改陣列時都會收到KVO通知。但是,KVO通知似乎只發送一次;數組第一次被修改。KVC/KVO和綁定:爲什麼我只收到一個更改通知?

我在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通知僅在第一次發送(並且在應用程序退出時再次發送)。爲什麼會這樣呢?

更新:

感謝orque您指出我應該結合我NSArrayControllercontentArray,不只是它的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]; 

然後,直接的方法是隻使用在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],你可以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,並感謝您的一個非常詳細的答案。我把我的綁定改爲「contentArray」而不是「內容」,所有事情都像魅力一樣。至於通過控制器更改陣列:這是一個簡化的例子。在我的真實應用程序中,該數組是模型對象上的一個屬性,並通過另一個進程進行修改。如果我要使用arrayController來修改我的對象,我的Model類將需要耦合到我的Controller類,這完全違背了MVC模式。 – 2009-08-21 21:14:38

+0

按鈕動作是'add:',而不是'addObject:'(它會添加按鈕!)。 – 2009-08-21 23:11:24

5

比明確發佈全值KVO通知更好的方法是實現array accessors並使用它們。然後KVO免費發佈通知。

這樣的,而不是這樣的:

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

你可以這樣做:

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

不僅將志願發佈變更通知你,但是這將是一個更具體的通知,是一個數組插入更改而不是整個數組的更改。

我通常添加的addThingsObject:方法,做上述,這樣我可以這樣做:

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

注意add<Key>Object:當前未用於陣列屬性(僅設置屬性)一個KVC公認的選擇的格式,而insertObject:in<Key>AtIndex:是,所以你的執行前(如果你選擇那樣做)必須使用後者。

+0

謝謝!看來我必須做一些讀取數組訪問器。這聽起來像是我對綁定理解中缺失的部分。只是爲了澄清:我可以有幾個'NSMutableArray'屬性,每個都需要自己的'insertObject:在 AtIndex:方法? – 2009-08-21 23:40:22

+0

沒錯。我有一對腳本,我作爲服務運行(使用ThisService),它採用伊娃聲明並生成大部分對其有用的訪問器。如今,只適用於數組和屬性。 http://boredzo.org/make-objc-accessors/ – 2009-08-22 00:48:36

+0

所以你要複製伊娃的聲明(例如,「NSMutableArray * mutableArray;'」),將其粘貼到標題中,選擇你剛剛粘貼的內容,運行Make Obj-C訪問器聲明服務,將相同的伊娃聲明粘貼到實現中,選擇你剛剛粘貼的內容,然後運行Make Obj-C訪問器定義服務。 – 2009-08-22 00:51:31

0

哦,我一直在尋找這個解決方案很長一段時間!謝謝大家 ! 獲得的想法&玩弄後,我發現了另一種非常奇特的方式:

假設我有一個對象CubeFrames這樣的:

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

我的數組包含Cubeframes的對象,它們通過管理(MVC)通過一個objectController並顯示在一個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); 
} 

現在,的確,我抓住所有的變化:添加和刪除行,變化在循環或數字:-)

相關問題