I have window controller and view controller that use core data bindings, but I want to be able to have those views really, truly get deallocated. I then want to reset
the managedObjectContext and at that point have given up as much memory as possible.
I have found that I am required to unbind
things which I've bound in the Nib, or the MOC keeps the Nib objects retained.
The issue is this EXEC_BAD_ACCESS trace:
If I do not unbind all of the bindings, even those created in Interface Builder, reset
on the MOC causes an EXEC_BAD_ACCESS because bindings are attempting to reflect changes in the MOC in the view that's gone, through an array controller that should be gone, but isn't.
So I did this in the window controller's dealloc:
- (void) dealloc
{
NSLog(@"Wincon dealloc");
@autoreleasepool {
// Remove subview to ensure subview dealloc
[_viewController.view removeFromSuperviewWithoutNeedingDisplay];
// Tear down bindings to ensure MOC can reset
for (NSObject<NSKeyValueBindingCreation>* object in
@[_watcherAC, _watchersTimesTreeController, _watcherTableView, _itemsOutlineView])
{
for (NSString* binding in [object exposedBindings])
[object unbind:binding];
}
}
}
which is triggered this way:
- (void) switchToBackgroundMode
{
NSLog(@"SwitchToBackgroundMode");
// Hide the menu and dock icon
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
// Force every view to deallocate before reset
@autoreleasepool {
// Need to check loaded to prevent closing a closed window and
// triggering a second call to applicationShouldTerminateAfterLastWindowClosed
if ([self.wincon isWindowLoaded]) [self.wincon close];
self.wincon = nil;
}
NSLog(@"About to resetCoreDataStack");
[self resetCoreDataStack];
}
... and now I don't get any errors with that resetCoreDataStack
The stack trace above comes with a log file like this:
2014-05-29 15:54:35.794 MyApp[10230:303] Switch to BG in appShouldTerminate
2014-05-29 15:54:35.794 MyApp[10230:303] SwitchToBackgroundMode
2014-05-29 15:54:35.808 MyApp[10230:303] Wincon dealloc
2014-05-29 15:54:35.830 MyApp[10230:303] About to resetCoreDataStack
2014-05-29 15:54:35.830 MyApp[10230:303] Reset Core Data
{Exception thrown iff wincon dealloc doesn't unbind everything}
And so the window controller dealloc is definitely called when it's nilled in the autoreleasepool, but MOC reset causes an EXEC_BAD_ACCESS unless that wincon dealloc does unbind
on a bunch of crap in the Nib.
So the question is:
Given a Nib owned by a custom window controller (self.wincon
) with arrayController objects bound to an external managedObjectContext, what needs to be done to force everything in the Nib to be released and unbound? Is there some step that I'm missing that causes me to have to do this unbinding manually?
[EDIT] Some new debug code:
NSLog(@"Wincon dealloc");
@autoreleasepool {
// Remove subview to ensure subview dealloc
[_viewController.view removeFromSuperviewWithoutNeedingDisplay];
_viewController = nil;
self.window = nil;
}
@autoreleasepool {
// Tear down bindings to ensure MOC can reset
for (NSObject<NSKeyValueBindingCreation>* object in
@[_watcherAC, _watchersTimesTreeController, _watcherTableView, /*_itemsOutlineView*/])
{
NSLog(@"Bindings for %@", [object className]);
for (NSString* binding in [object exposedBindings]) {
NSLog(@"BI for %@: %@", binding, [object infoForBinding:binding]);
[object unbind:binding];
}
}
the log below is the bindingInfo for the bindings still alive when dealloc is called for the windowController
2014-05-29 21:00:39.967 SaleWatch[11249:303] Wincon dealloc
2014-05-29 21:00:39.975 SaleWatch[11249:303] Bindings for NSArrayController
2014-05-29 21:00:39.978 SaleWatch[11249:303] Bindings for NSTreeController
2014-05-29 21:00:39.989 SaleWatch[11249:303] BI for contentSet: {
NSObservedKeyPath = "selection.fetchTimesForOutlineView";
NSObservedObject = "[entity: SWWebStoreWatcher, number of selected objects: 1]";
}
2014-05-29 21:00:39.991 SaleWatch[11249:303] Bindings for NSTableView
2014-05-29 21:00:39.991 SaleWatch[11249:303] BI for selectionIndexes: {
NSObservedKeyPath = selectionIndexes;
NSObservedObject = "[entity: SWWebStoreWatcher, number of selected objects: 1]";
}
2014-05-29 21:00:40.001 SaleWatch[11249:303] BI for content: {
NSObservedKeyPath = arrangedObjects;
NSObservedObject = "[entity: SWWebStoreWatcher, number of selected objects: 1]";
}
2014-05-29 21:00:40.020 SaleWatch[11249:303] About to resetCoreDataStack
I forced wincon.window = nil in the new code, and these three objects still aren't nil, though the outlineView the treeController is for did become nil. There could be a retain cycle here, but I don't see how it'd be my fault... yet.
See Question&Answers more detail:
os