Je vois un comportement bizarre avec KVC/KVO de Cocoa et les liaisons. J'ai un objet NSArrayController
, avec son 'contenu' lié à un NSMutableArray
, et j'ai un contrôleur enregistré en tant qu'observateur de la propriété arrangedObjects
sur le NSArrayController
. Avec cette configuration, je m'attends à recevoir une notification KVO chaque fois que le tableau est modifié. Cependant, il semble que la notification KVO n'est envoyée qu'une seule fois; la toute première fois que le tableau est modifié.KVC/KVO et fixations: pourquoi ne reçois-je qu'une seule notification de modification?
J'ai mis en place un tout nouveau projet "Cocoa Application" dans Xcode pour illustrer le problème. Voici mon code:
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
Et voici la sortie:
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
Comme vous pouvez le voir , la La notification KVO est uniquement envoyée la première fois (et une fois de plus lorsque l'application se termine). Pourquoi cela serait-il le cas?
mise à jour:
Merci à orque de remarquer que je lie au contentArray
de mon NSArrayController
, non seulement son content
. Le code ci-dessus affiché fonctionne, dès ce changement:
// bind the arrayController to the array
[arrayController bind:@"contentArray" // <-- the change was made here
toObject:self
withKeyPath:@"mutableArray"
options:0];
+1 et merci pour une réponse très détaillée. J'ai changé ma liaison à "contentArray" au lieu de "content", et tout a fonctionné comme un charme. Quant à changer le tableau à travers le contrôleur: C'était un exemple simplifié. Dans ma vraie application, le tableau est une propriété sur un objet modèle, et il est modifié par un autre processus. Si je devais utiliser arrayController pour modifier mon objet, mes classes Model devraient être couplées à mes classes Controller, ce qui va complètement à l'encontre du pattern MVC. –
L'action du bouton serait 'ajouter:', pas 'addObject:' (ce qui ajouterait le bouton!). –