Bindings: NSArrayController gets a new but equal array, pushes back to model
Bindings: NSArrayController gets a new but equal array, pushes back to model
- Subject: Bindings: NSArrayController gets a new but equal array, pushes back to model
- From: Jerry Krinock <email@hidden>
- Date: Tue, 16 Feb 2010 21:24:20 -0800
This is a resolution of my message posted 2010 Jan 02, subject:
Bindings/Core Data: Undesired Discovery of a Mythical "Deep Observer"
I'm posting this because, after rewriting the whole thing and giving it some more thought, I believe I have an explanation, which archive-searchers might find interesting. Summary: If an NSArrayController with 'contentArray' bound to a data model receives from the data model a new array which is equal to but a different pointer value than the array it currently has, it will push the new array back through the setter of the data model.
I'm working on a Core Data project. Some to-many properties are displayed in tables, and these objects in turn have their own attributes which are displayed in the columns, bound to array controllers' -arrangedObjects.xxx, etc. Pretty standard stuff...
DEPARTMENT's EMPLOYEES
Name Rank Salary
------- ---- ------
Fat Cat 3 200
except that I have subclassed NSManagedObject and added 'index' attributes so that I can treat the sets as arrays, providing methods, for example -(NSArray*)employeesOrdered, -(Employee*)newEmployeeAtIndex:, etc. All this works OK, except in a few of my columns I notice that, taking the above example, if user edits the table to change, say, the 'rank' of Fat Cat, after sending setRank:, the *Department* gets a -setEmployeesOrdered: message, with an NSArray argument which is a different pointer value but otherwise equal to the existing employeesOrdered array; it contains the same single object with the same pointer value.
This does no harm to the data model of course; the only reason I noticed it is because in some cases it overwrites and thus screws up my undo action names which are driven by custom setters.
In real life, -setEmployeesOrdered: is actually -setExternalizersOrdered and is #9 in the call stack below. To fix the problem, in this setter I simply first check for array equality and return if no change. Looking at the calls lower down, it appears to be fulfilling the binding on the array controller which causes this unnecessary message. Of course, I do have an array controller with contentArray bound to 'externalizersOrdered'. Also, it makes sense that there would be different pointer values of externalizersOrdered floating around since the getter computes it from the underlying set. See code at the end.
I've decided that, apparently what's happening is that when when the array controller notices that a bound (by a table column) to attribute is changed, it asks the data model for -externalizersOrdered, sees that it gets a different array, but does not bother to check and see that the new array, although a different pointer value, is equal to the old array. Probably it only keeps a reference. And then due to some quirk in bindings it says, "Oh, I better push this new array to the data model's setter" (even though it just got it from the data model).
If anyone has read this far and has a better explanation, let us know.
#0 0x00122961 in -[Bkmslf setUndoActionNameForAction:object:objectKey:updatedKey:count:] at Bkmslf.m:2928
#1 0x0001fd71 in -[Bookshig mikeAshObserveValueForKeyPath:ofObject:change:userInfo:] at Bookshig.m:1789
#2 0x000ff452 in -[MAKVObservation observeValueForKeyPath:ofObject:change:context:] at MAKVONotificationCenter.m:98
#3 0x002d6208 in NSKeyValueNotifyObserver
#4 0x002d5ca7 in NSKeyValueDidChange
#5 0x002ba6d0 in -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:]
#6 0x01baa375 in -[NSManagedObject didChangeValueForKey:]
#7 0x0006e3fb in -[Ixternalizer setIndex:] at Ixternalizer.m:351
#8 0x000b6d93 in -[SSYManagedObject setWithIndexesArray:forSetKey:] at SSYManagedObject.m:231
#9 0x0001d7f5 in -[Bookshig setExternalizersOrdered:] at Bookshig.m:1233
#10 0x002dec99 in _NSSetObjectValueAndNotify
#11 0x01ba726a in -[NSManagedObject setValue:forKey:]
#12 0x002eead3 in -[NSObject(NSKeyValueCoding) setValue:forKeyPath:]
#13 0x002eeaaf in -[NSObject(NSKeyValueCoding) setValue:forKeyPath:]
#14 0x002eeaaf in -[NSObject(NSKeyValueCoding) setValue:forKeyPath:]
#15 0x0078f0d6 in -[NSBinder _setValue:forKeyPath:ofObject:mode:validateImmediately:raisesForNotApplicableKeys:error:]
#16 0x0078eeb0 in -[NSBinder setValue:forBinding:error:]
#17 0x00b14745 in -[NSObjectDetailBinder setMasterObjectRelationship:refreshDetailContent:]
#18 0x00b1461d in -[NSObjectDetailBinder noteContentValueHasChanged]
#19 0x008f27ac in -[NSArrayController _setMultipleValue:forKeyPath:atIndex:]
#20 0x0078f211 in -[NSBinder _setValue:forKeyPath:ofObject:mode:validateImmediately:raisesForNotApplicableKeys:error:]
#21 0x00903832 in -[NSBinder setValue:forBinding:atIndex:error:]
#22 0x0078ed3d in -[_NSValueBinderPlugin applyObjectValue:forBinding:operation:needToRunAlert:error:]
#23 0x00ca9a4d in -[NSValueBinder _applyObjectValue:forBinding:canRecoverFromErrors:handleErrors:typeOfAlert:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:]
#24 0x00ca98a3 in -[NSValueBinder applyDisplayedValueHandleErrors:typeOfAlert:canRecoverFromErrors:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:]
#25 0x0078e647 in -[NSValueBinder performAction:]
#26 0x0078e3f6 in -[_NSBindingAdaptor _objectDidTriggerAction:bindingAdaptor:]
#27 0x0078e333 in -[_NSBindingAdaptor objectDidTriggerAction:]
#28 0x007926d9 in -[NSControl sendAction:to:]
#29 0x0078e1ba in -[NSCell _sendActionFrom:]
#30 0x006b2f86 in -[NSApplication sendAction:to:from:]
#31 0x006b2e39 in -[NSMenuItem _corePerformAction]
#32 0x006b2b2a in -[NSCarbonMenuImpl performActionWithHighlightingForItemAtIndex:]
#33 0x006b2a16 in -[NSMenu performActionForItemAtIndex:]
#34 0x006b29c9 in -[NSMenu _internalPerformActionForItemAtIndex:]
#35 0x006b292f in -[NSMenuItem _internalPerformActionThroughMenuIfPossible]
#36 0x006b2873 in -[NSCarbonMenuImpl _carbonCommandProcessEvent:handlerCallRef:]
#37 0x006a6f79 in NSSLMMenuEventHandler
#38 0x03396e29 in DispatchEventToHandlers
#39 0x033960f0 in SendEventToEventTargetInternal
#40 0x033b8981 in SendEventToEventTarget
#41 0x033e4e3b in SendHICommandEvent
#42 0x03409b20 in SendMenuCommandWithContextAndModifiers
#43 0x03409ad7 in SendMenuItemSelectedEvent
#44 0x034099d3 in FinishMenuSelection
#45 0x0358ab82 in PopUpMenuSelectCore
#46 0x0358aed0 in _HandlePopUpMenuSelection7
#47 0x0093c61e in _NSSLMPopUpCarbonMenu3
#48 0x00b30a32 in -[NSPopUpButtonCell trackMouse:inRect:ofView:untilMouseUp:]
#49 0x007efa02 in -[NSTableView _tryCellBasedMouseDown:atRow:column:withView:]
#50 0x007ec79f in -[NSTableView mouseDown:]
#51 0x00789f10 in -[NSWindow sendEvent:]
#52 0x000b2684 in -[SSYDocChildWindow sendEvent:] at SSYDocChildWindow.m:18
#53 0x006a2b2f in -[NSApplication sendEvent:]
#54 0x006364ff in -[NSApplication run]
#55 0x0062e535 in NSApplicationMain
#56 0x00001e67 in main at MainApp-Main.m:19
- (NSArray*)externalizersOrdered {
return [[self externalizers] arraySortedByKeyPath:constKeyIndex] ;
}
- (void)setExternalizersOrdered:(NSArray*)externalizersOrdered {
// Patch to ignore re-setting to an equal array:
if ([externalizersOrdered isEqualToArray:[self externalizersOrdered]]) {
return ;
}
[self setWithIndexesArray:externalizersOrdered
forSetKey:constKeyExternalizers] ;
}
+ (NSSet *)keyPathsForValuesAffectingExternalizersOrdered {
return [NSSet setWithObject:constKeyExternalizers] ;
}
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden