How could I really sync NSMutableArray with NSArrayController?
I've got class Dialog and NSArrayController in my UI which bind Content Array to Dialog.messages
. When I add any new items directly in Dialog.messages
they are also available in NSArrayController and everything looks good:
[Dialog addMessage: someMsgItem];
But I also need sort messages and it's works only for Dialog.messages
and not for NSArrayController:
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey: @"timestamp" ascending: YES];
_messages = [NSMutableArray arrayWithArray: [_messages sortedArrayUsingDescriptors: @[sort]]];
How can I make these changes are also available for my NSArrayController? This is a part of Dialog class implementation:
@implementation Dialog
@synthesize messages = _messages; // NSMutableArray
- (void) addMessage:(Message *)msg {
[self insertObject:msg inMessagesAtIndex:[_messages count]];
}
- (void) removeMessage:(Message *)msg {
[self removeObjectFromMessagesAtIndex:[self.messages indexOfObject:msg]];
}
- (NSArray *) messages {
return [_messages copy];
}
- (void) setMessages:(NSArray *)messages {
[self willChangeValueForKey:@"messages"];
_messages = [NSMutableArray arrayWithArray:messages];
[self didChangeValueForKey:@"messages"];
}
- (void) insertObject:(Message *)msg inMessagesAtIndex:(NSUInteger)index {
[_messages insertObject:msg atIndex:index];
}
- (void) removeObjectFromMessagesAtIndex:(NSUInteger)index {
[_messages removeObjectAtIndex:index];
}
@end
You don't need to sort the _messages
array. Set sortDescriptors
on the array controller and its arrangedObjects
will be sorted according to those descriptors. Then, whatever UI (table view?) is bound to the array controller's arrangedObjects
will automatically pick up the sorted order.
The complication is that when you need to look up the model object corresponding to a UI element (e.g. row in a table view), you need to index into the array controller's arrangedObjects
, not _messages
.
If you really want to sort _messages
:
Don't replace it like the code you showed. Mutable arrays can be sorted in place, like [someMutableArray sortUsingDescriptors:@[sort]]
.
You have to make sure that KVO change notifications are emitted when you change _messages
other than by a call to one of the KVO-compliant accessor methods. There are two ways to do this:
a. Call -willChangeValueForKey:
and -didChangeValueForKey:
. For example:
[self willChangeValueForKey:@"messages"];
[_messages sortUsingDescriptors:@[sort]];
[self didChangeValueForKey:@"messages"];
I don't recommend this option. It's error prone and is sure to generate the least efficient change notification.
b. Operate on the mutable-array-like proxy returned from -mutableArrayValueForKey:
. For example:
[[self mutableArrayValueForKey:@"messages"] sortUsingDescriptors:@[sort]];
There's at least the chance that this will generate more efficient change notifications (with NSKeyValueChangeKindKey
being NSKeyValueChangeReplacement
).
As an aside, you don't need to call -willChangeValueForKey:
and -didChangeValueForKey:
in your -setMessages:
method and, in fact, you shouldn't. Since that method conforms to the normal naming conventions for the setter of a property called "messages", KVO automatically hooks into it and generates the appropriate change notifications. (If you want it to not do that, you would have to override +automaticallyNotifiesObserversForKey:
to return false for that key.)