NSTableView crashes in Custom View that is added to a SplitView - crash

My intent is to have 2 separate view controllers for a NSSplitView with two panes. In the left pane I want a table view, but it crashes. Here's the scenario:
I have a simple project with a MainMenu.xib, AppDelegate.h/m. In this project I add a LeftPaneViewController.h/.m/.xib.
In MainMenu.xib I add a NSSplitView to the default view.
In AppDelegate.h:
#property (weak) IBOutlet NSSplitView *splitView;
In AppDelegate.m:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
LeftPaneViewController *lpvc = [[LeftPaneViewController alloc] initWithNibName:#"LeftPaneViewController" bundle:nil];
[self.splitView replaceSubview:[[self.splitView subviews] objectAtIndex:0] with:lpvc.view];
}
I connect the NSSplitView in MainMenu.xib to splitView in the AppDelegate.
When I run this works fine, but of course there is nothing to see yet.
In LeftPaneViewController.xib I add a NSTableView to the default custom view. I delete one of the columns (the bottom one in the list).
In LeftPaneViewController.h:
#interface LeftPaneViewController : NSViewController <NSTableViewDelegate, NSTableViewDataSource>
#property (weak) IBOutlet NSTableView *tableView;
#property (nonatomic, retain) NSMutableArray *tableDataSource;
In LeftPaneViewController.m:
#import "LeftPaneViewController.h"
#interface LeftPaneViewController ()
#end
#implementation LeftPaneViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
}
return self;
}
- (void)awakeFromNib
{
self.tableDataSource = [[NSMutableArray alloc] initWithObjects:#"Row1", #"Row2", nil];
[self.tableView setNeedsDisplay];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
// This is a defensive move
// There are cases where the nib containing the NSTableView can be loaded before the data is populated
// by ensuring the count value is 0 and checking to see if the namesArray is not nil, the app
// is protecting itself agains that situation
NSInteger count=0;
if (self.tableDataSource)
count=[self.tableDataSource count];
return count;
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// Get an existing cell with the MyView identifier if it exists
NSTextField *result = [tableView makeViewWithIdentifier:#"LeftPaneCell" owner:self];
// There is no existing cell to reuse so create a new one
if (result == nil) {
// Create the new NSTextField with a frame of the {0,0} with the width of the table.
// Note that the height of the frame is not really relevant, because the row height will modify the height.
NSTextField *textField;
textField = [[NSTextField alloc] initWithFrame:NSMakeRect(2, 456, 125, 20)];
[textField setStringValue:#"My Label"];
[textField setBezeled:NO];
[textField setDrawsBackground:NO];
[textField setEditable:NO];
[textField setSelectable:NO];
[self.view addSubview:textField];
// The identifier of the NSTextField instance is set to MyView.
// This allows the cell to be reused.
result.identifier = #"LeftPaneCell";
}
// result is now guaranteed to be valid, either as a reused cell
// or as a new cell, so set the stringValue of the cell to the
// nameArray value at row
result.stringValue = [self.tableDataSource objectAtIndex:row];
// Return the result
return result;
}
#end
In the LeftPaneViewController.xib I hook the TableView dataSource and delegate to "File's Owner" (of LeftPaneViewController of course). and hook the tableView referencing outlet to the tableView in LeftPaneViewController.h. I checked these at least a thousand times ;-)
Then when I run I get the following crash log:
* thread #1: tid = 0x23427, 0x00007fff82893250 libobjc.A.dylib`objc_msgSend + 16, queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
frame #0: 0x00007fff82893250 libobjc.A.dylib`objc_msgSend + 16
frame #1: 0x00007fff88f8d972 AppKit`-[NSTableRowData _addViewToRowView:atColumn:row:] + 324
frame #2: 0x00007fff88f8d63f AppKit`-[NSTableRowData _addViewsToRowView:atRow:] + 151
frame #3: 0x00007fff88f8bbd5 AppKit`-[NSTableRowData _addRowViewForVisibleRow:withPriorView:] + 415
frame #4: 0x00007fff88f8b95a AppKit`-[NSTableRowData _addRowViewForVisibleRow:withPriorRowIndex:inDictionary:withRowAnimation:] + 272
frame #5: 0x00007fff88f8ac29 AppKit`-[NSTableRowData _unsafeUpdateVisibleRowEntries] + 740
frame #6: 0x00007fff88f8a7c1 AppKit`-[NSTableRowData updateVisibleRowViews] + 119
frame #7: 0x00007fff88f625a7 AppKit`-[NSTableView layout] + 165
frame #8: 0x00007fff88f15e65 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 112
frame #9: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #10: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #11: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #12: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #13: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #14: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #15: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #16: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #17: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #18: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #19: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #20: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #21: 0x00007fff88f15cfe AppKit`-[NSView layoutSubtreeIfNeeded] + 615
frame #22: 0x00007fff88f114ac AppKit`-[NSWindow(NSConstraintBasedLayout) layoutIfNeeded] + 201
frame #23: 0x00007fff88e0b0a8 AppKit`_handleWindowNeedsDisplayOrLayoutOrUpdateConstraints + 446
frame #24: 0x00007fff893d6901 AppKit`__83-[NSWindow _postWindowNeedsDisplayOrLayoutOrUpdateConstraintsUnlessPostingDisabled]_block_invoke_01208 + 46
frame #25: 0x00007fff8d0e9417 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
frame #26: 0x00007fff8d0e9381 CoreFoundation`__CFRunLoopDoObservers + 369
frame #27: 0x00007fff8d0c47b8 CoreFoundation`__CFRunLoopRun + 728
frame #28: 0x00007fff8d0c40e2 CoreFoundation`CFRunLoopRunSpecific + 290
frame #29: 0x00007fff8c17aeb4 HIToolbox`RunCurrentEventLoopInMode + 209
frame #30: 0x00007fff8c17ab94 HIToolbox`ReceiveNextEventCommon + 166
frame #31: 0x00007fff8c17aae3 HIToolbox`BlockUntilNextEventMatchingListInMode + 62
frame #32: 0x00007fff88e08533 AppKit`_DPSNextEvent + 685
frame #33: 0x00007fff88e07df2 AppKit`-[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 128
frame #34: 0x00007fff88dff1a3 AppKit`-[NSApplication run] + 517
frame #35: 0x00007fff88da3bd6 AppKit`NSApplicationMain + 869
frame #36: 0x0000000100001872 TableView_In_SplitView`main(argc=3, argv=0x00007fff5fbff7e8) + 34 at main.m:13
frame #37: 0x00007fff84d4d7e1 libdyld.dylib`start + 1
With breakpoints I determine that in LeftPaneViewController.m the numberOfRowsInTableView is called and returns 2, but viewForTableColumn is not called.
The TableView is Cell Based so I do expect the viewForTableColumn to be called.
If I simply add a NSTableView to the SplitView in MainMenu.xib and transfer the same code into AppDelegate.h/.m it works fine (and changing the hooks as well of course). That is, it does not crash and both numberOfRowsInTableView and viewForTableColumn are called.
So what am I not doing that is causing the crash?

Ok, found the answer. Since the LeftPaneViewController variable (lpvc) in AppDelegate.m was local it was being released after the replaceSubview call. So adding it as (nonatomic, retain) in AppDelegate.h fixed the crash. Turned on Zombies to find the answer ;-).

Related

Strange Cocoa crash on every ~10th run in the event loop

I'm building a simple Cocoa app from scratch, and right now I have a working app with one window.
The problem is it crashes on every ~10th run in [NSApp nextEventMatchingMask]. Here's the code:
#include <Cocoa/Cocoa.h>
void wait_for_events() {
NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:[NSDate distantFuture]
inMode:NSDefaultRunLoopMode
dequeue:YES];
[NSApp sendEvent:event];
}
int main() {
NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp finishLaunching];
NSUInteger windowStyle =
NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask;
NSWindow *window =
[[NSWindow alloc] initWithContentRect:NSMakeRect(0,0,300,300)
styleMask:windowStyle
backing:NSBackingStoreBuffered
defer:NO];
[window center];
[window retain];
[window orderFrontRegardless];
//[NSApp run];
while (1) {
wait_for_events();
}
}
I need access directly to the event loop, so I can't call [NSApp run]. I copied wait_for_events code from glfw.
I tried adding autorelease pool and disabling ARC via -fno-objc-arc
I tried this on 2 different machines with Sierra and High Sierra and still getting random EXC_BAD_ACCESS crashes:
Process 4925 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
frame #0: 0x00007fff50049290 libobjc.A.dylib`objc_destructInstance + 32
libobjc.A.dylib`objc_destructInstance:
-> 0x7fff50049290 <+32>: andq 0x20(%rax), %rcx
0x7fff50049294 <+36>: movb 0x2(%rcx), %cl
0x7fff50049297 <+39>: andb $0x2, %cl
0x7fff5004929a <+42>: shrb %cl
Target 0: (a.out) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
* frame #0: 0x00007fff50049290 libobjc.A.dylib`objc_destructInstance + 32
frame #1: 0x00007fff2973a201 CoreFoundation`_CFRelease + 1025
frame #2: 0x00007fff29600cf4 CoreFoundation`__CFBasicHashDrain + 388
frame #3: 0x00007fff29739f1c CoreFoundation`_CFRelease + 284
frame #4: 0x00007fff2967f68c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
frame #5: 0x00007fff296620a3 CoreFoundation`__CFRunLoopDoBlocks + 275
frame #6: 0x00007fff2966172e CoreFoundation`__CFRunLoopRun + 1278
frame #7: 0x00007fff29660fa3 CoreFoundation`CFRunLoopRunSpecific + 483
frame #8: 0x00007fff28980866 HIToolbox`RunCurrentEventLoopInMode + 286
frame #9: 0x00007fff289804df HIToolbox`ReceiveNextEventCommon + 366
frame #10: 0x00007fff28980354 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 64
frame #11: 0x00007fff26c7da23 AppKit`_DPSNextEvent + 2085
frame #12: 0x00007fff27412e6c AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 3044
frame #14: 0x0000000100011ef9 a.out`wait_for_events + 9
frame #15: 0x0000000100016030 a.out`main + 7
frame #16: 0x00000001000013f4 a.out`start + 52
I figured it out. There was another function with an uninitialized pointer.
I didn't even realize it was being called.
Somehow lldb didn't help me track it down.

NSView Frame Reverting After Being Set Programmatically

So I have a view that I set in IB and I need to change the frame programmatically. For some reason, the frame keeps reverting back to its IB location after I've set it. I subclassed NSView and logged the frame in the -setFrame:(NSRect)frameRect method and it looks like -setFrame: is getting called twice -- once when I set it (where it logs the new values) and once when it reverts (where it logs the IB values). I can't seem to distill the root of the problem because in some situations (such as if I have an NSButton dedicated to setting it or have a timer setting the frame) it works perfectly, but if I have the -setFrame: call in-line with my other code, it always reverts.
Edit:
This is a simple example that shows the problem (the original frame in the IB is {{20, 118}, {48, 48}}):
AppDelegate.m:
#import "AppDelegate.h"
#implementation AppDelegate
- (void)awakeFromNib{
[self.button setFrame:NSMakeRect(50, 10, 100, 100)];
}
#end
Log:
2014-02-18 18:01:40.206 WHS-ChangingFrameTest[15210:303] Frame: {{50, 10}, {100, 100}}
2014-02-18 18:01:41.223 WHS-ChangingFrameTest[15210:303] Frame: {{20, 118}, {48, 48}}
Edit #2:
Call Stack from when I edit the frame (from original app):
0 MyApp 0x000000010000203c -[FrameLogProgressIndicator setFrame:] + 284
1 MyApp 0x000000010001c994 -[SubjectViewController updateTableViewHeight] + 1284
2 MyApp 0x000000010001c468 -[SubjectViewController updateUI] + 4664
3 MyApp 0x0000000100012f2f -[TabMenuViewController updateDisplayingBlock:] + 975
4 MyApp 0x0000000100010c59 -[TabMenuViewController switchBlockFromDaySchedulePopover:] + 873
5 AppKit 0x00007fff82eea959 -[NSApplication sendAction:to:from:] + 342
6 AppKit 0x00007fff82eea7b7 -[NSControl sendAction:to:] + 85
7 AppKit 0x00007fff82eea6eb -[NSCell _sendActionFrom:] + 138
8 AppKit 0x00007fff82ee8bd3 -[NSCell trackMouse:inRect:ofView:untilMouseUp:] + 1855
9 AppKit 0x00007fff82ee8421 -[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:] + 504
10 AppKit 0x00007fff82ee7b9c -[NSControl mouseDown:] + 820
11 AppKit 0x00007fff82edf50e -[NSWindow sendEvent:] + 6853
12 AppKit 0x00007fff82edb644 -[NSApplication sendEvent:] + 5761
13 AppKit 0x00007fff82df121a -[NSApplication run] + 636
14 AppKit 0x00007fff82d95bd6 NSApplicationMain + 869
15 MyApp 0x00000001000020a2 main + 34
16 libdyld.dylib 0x00007fff8152a7e1 start + 0
17 ??? 0x0000000000000003 0x0 + 3
)
Call Stack from when frame is reverting back:
0 MyApp 0x000000010000203c -[FrameLogProgressIndicator setFrame:] + 284
1 AppKit 0x00007fff82e21e77 -[NSView resizeWithOldSuperviewSize:] + 659
2 AppKit 0x00007fff82e21307 -[NSView resizeSubviewsWithOldSize:] + 318
3 AppKit 0x00007fff82f08399 NSViewLevelLayout + 44
4 AppKit 0x00007fff82f07e65 -[NSView _layoutSubtreeHeedingRecursionGuard:] + 112
5 CoreFoundation 0x00007fff84b524a6 __NSArrayEnumerate + 582
6 AppKit 0x00007fff82f07fc6 -[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
7 CoreFoundation 0x00007fff84b524a6 __NSArrayEnumerate + 582
8 AppKit 0x00007fff82f07fc6 -[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
9 CoreFoundation 0x00007fff84b524a6 __NSArrayEnumerate + 582
10 AppKit 0x00007fff82f07fc6 -[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
11 CoreFoundation 0x00007fff84b524a6 __NSArrayEnumerate + 582
12 AppKit 0x00007fff82f07fc6 -[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
13 AppKit 0x00007fff82f07cfe -[NSView layoutSubtreeIfNeeded] + 615
14 AppKit 0x00007fff82f034ac -[NSWindow(NSConstraintBasedLayout) layoutIfNeeded] + 201
15 AppKit 0x00007fff82dfd0a8 _handleWindowNeedsDisplayOrLayoutOrUpdateConstraints + 446
16 AppKit 0x00007fff833c8901 __83-[NSWindow _postWindowNeedsDisplayOrLayoutOrUpdateConstraintsUnlessPostingDisabled]_block_invoke_01208 + 46
17 CoreFoundation 0x00007fff84b20417 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
18 CoreFoundation 0x00007fff84b20381 __CFRunLoopDoObservers + 369
19 CoreFoundation 0x00007fff84afb7b8 __CFRunLoopRun + 728
20 CoreFoundation 0x00007fff84afb0e2 CFRunLoopRunSpecific + 290
21 HIToolbox 0x00007fff8231aeb4 RunCurrentEventLoopInMode + 209
22 HIToolbox 0x00007fff8231ab94 ReceiveNextEventCommon + 166
23 HIToolbox 0x00007fff8231aae3 BlockUntilNextEventMatchingListInMode + 62
24 AppKit 0x00007fff82dfa533 _DPSNextEvent + 685
25 AppKit 0x00007fff82df9df2 -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 128
26 AppKit 0x00007fff82df11a3 -[NSApplication run] + 517
27 AppKit 0x00007fff82d95bd6 NSApplicationMain + 869
28 MyApp 0x00000001000020a2 main + 34
29 libdyld.dylib 0x00007fff8152a7e1 start + 0
30 ??? 0x0000000000000003 0x0 + 3
)
Let me know if I need to post the code from the methods used in the stack for this to be useful. (sorry, I've never really dealt with this stuff before)
An alternative to replacing the content of resizeWithOldSuperviewSize: is to inform the auto layout system that you don't want your NSView to be resized. This will have the effect of keeping your NSView at the origin that you specified programmatically thereby keeping your override of the interface builder intact. You do this by:
[<id> setAutoresizingMask:NSViewNotSizable];
[<id> setTranslatesAutoresizingMaskIntoConstraints:YES];
where <id> would be the instance of your NSView, i.e. self.button. The first line states that the view is not sizable, while the second states that the mask should be considered a constraint by the auto layout system. Your revised AppDelegate.m would then be:
#import "AppDelegate.h"
#implementation AppDelegate
- (void)awakeFromNib{
[self.button setAutoresizingMask:NSViewNotSizable];
[self.button setTranslatesAutoresizingMaskIntoConstraints:YES];
[self.button setFrame:NSMakeRect(50, 10, 100, 100)];
}
#end
Update:
If you use this method with an NSView that you plan to set hidden, the auto layout system will still take the frame of the hidden NSView into account when the frame's superview/window is resized. This means that if the hidden NSView would be outside the visible area of the superview after resize, the auto layout system will prevent the superview from resizing properly and instead will force the superview frame to enclose the hidden NSView.
One naïve solution to this problem is to set the width and height of the NSView to zero after setting hidden:YES and restoring the width and height before setting hidden:NO. For example, at some point in your code using your NSView self.button:
...
[self.button setHidden:YES];
[self.button setFrameSize:NSZeroSize];
...
and later:
...
[self.button setFrameSize:NSMakeSize(160, 90)];
[self.button setHidden:NO];
...
However, if you have width/height auto layout constraints set on the NSView (either programmatically or through Interface Builder), these changes may throw warnings like:
Unable to simultaneously satisfy constraints:
(
"<NSLayoutConstraint:0x608000082990 H:[NSButton:0x6080001200a0(100)]>",
"<NSAutoresizingMaskLayoutConstraint:0x60800008bae0 h=--& v=--& H:[NSButton:0x6080001200a0(0)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x608000082990 H:[NSButton:0x6080001200a0(100)]>
You can either ignore these warnings, reduce the constraints priority to a low number, change the constraint from = to ≤, or alternately you can simply setTranslatesAutoresizingMaskIntoConstraints:NO before hiding the NSView, and setting it to YES prior to unhiding the NSView:
...
[self.button setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.button setHidden:YES];
...
And when we unhide the NSView:
...
[self.button setTranslatesAutoresizingMaskIntoConstraints:YES];
[self.button setHidden:NO];
...
You can also automate this by subclassing the NSView and overriding setHidden: (note the !):
- (void)setHidden:(BOOL)hidden {
[self setTranslatesAutoresizingMaskIntoConstraints:!hidden];
[super setHidden:hidden];
}
You can then simply call [self.button setHidden:YES]; or [self.button setHidden:NO]; and the overridden method will take care of everything.
From awakeFromNib description:
Because the order in which objects are instantiated from an archive is not guaranteed, your initialization methods should not send messages to other objects in the hierarchy. Messages to other objects can be sent safely from within an awakeFromNib method.
Typically, you implement awakeFromNib for objects that require additional set up that cannot be done at design time. For example, you might use this method to customize the default configuration of any controls to match user preferences or the values in other controls. You might also use it to restore individual controls to some previous state of your application.
I am not 100% sure but I am strongly suggest to move
[self.button setFrame:NSMakeRect(50, 10, 100, 100)];
to
- (void)viewDidLoad
method. Also you should not forget to call super's methods - in some cases it could be critical. So your final code should look like this:
#implementation AppDelegate
- (void)awakeFromNib{
[super awakeFromNib];
... non GUI initialization
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.button setFrame:NSMakeRect(50, 10, 100, 100)];
}
#end
UPDATE:
Thanks for call stuck. It seems that your view is autoresized. You should check autoresizingMask and auto-layout constraints
So I managed to stop the resizing of the views by subclassing them and overriding -resizeWithOldSuperviewSize: to just do nothing, like this:
- (void)resizeWithOldSuperviewSize:(NSSize)oldSize {};

EXC_BAD_ACCESS after NSWindowWillCloseNotification removes window from Array

Say I have a menu button called 'Create window' that creates a new window:
MyWindowClass * window = [MyWindowClass new];
In order to retain it, I add it to a mutable array (declared and synthesised as _articleArray = [NSMutableArray new];)
[_articleArray addObject:window]
This works great. If I include:
NSLog(#"Windows in mem: %lu",_articleArray.count);
The number increments each time that I click the button and another window appears on the screen.
Now, if I attach a selector to this 'create window' function to identify when the window closes:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(windowClosed:) name:NSWindowWillCloseNotification object:window];
This creates an error:
-(void) windowClosed:(NSNotification*)notification {
[_articleArray removeObject:[notification object]];
NSLog(#"Windows in mem: %lu",_articleArray.count);
The NSLog decrements when I close a window as expected, but as soon as the function ends, it throws an EXC_BAD_ACCESS error (code 13, address=0,0)
0x7fff97878710: movq 24(%rax), %rax
I am very confused. The number decrements, so I can only think that the function is working. So what is happening here?
EDIT: (lldb) thread backtrace
* thread #1: tid = 0x1c07, 0x00007fff97878710 libobjc.A.dylib`objc_msgSend_vtable13 + 16, stop reason = EXC_BAD_ACCESS (code=13, address=0x0)
frame #0: 0x00007fff97878710 libobjc.A.dylib`objc_msgSend_vtable13 + 16
frame #1: 0x00007fff97571503 Foundation`__NSFireTimer + 80
frame #2: 0x00007fff993a6da4 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
frame #3: 0x00007fff993a68bd CoreFoundation`__CFRunLoopDoTimer + 557
frame #4: 0x00007fff9938c099 CoreFoundation`__CFRunLoopRun + 1513
frame #5: 0x00007fff9938b6b2 CoreFoundation`CFRunLoopRunSpecific + 290
frame #6: 0x00007fff8df260a4 HIToolbox`RunCurrentEventLoopInMode + 209
frame #7: 0x00007fff8df25e42 HIToolbox`ReceiveNextEventCommon + 356
frame #8: 0x00007fff8df25cd3 HIToolbox`BlockUntilNextEventMatchingListInMode + 62
frame #9: 0x00007fff92ce3613 AppKit`_DPSNextEvent + 685
frame #10: 0x00007fff92ce2ed2 AppKit`-[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 128
frame #11: 0x00007fff92cda283 AppKit`-[NSApplication run] + 517
frame #12: 0x00007fff92c7ecb6 AppKit`NSApplicationMain + 869
frame #13: 0x0000000100006942 myApp`main + 34 at main.m:13
frame #14: 0x00007fff9094f7e1 libdyld.dylib`start + 1
This answer has been moved from the comments in the question at the request of the OP
You need to ensure that you remove your NSWindowWillCloseNotification observer before the window is destroyed:
-(void) windowClosed:(NSNotification*)notification {
NSWindow *window = [notification object];
[[NotificationCenter defaultCenter] removeObserver:self
name:NSWindowWillCloseNotification
object:window];
[_articleArray removeObject:window];
NSLog(#"Windows in mem: %lu",_articleArray.count);
...
And also ensure that the window has its isReleasedWhenClosed property set to YES so that it cleans-up after itself when closed.

EXC_BAD_ACCESS in heightForRowAtIndexPath iOS

I'm working on an application where I have a custom subclass of UITableViewCell. I want to make the cell's height dynamic based on the text inside it. I try do do that in my heightForRowAtIndexPath method. But I'm having some issues, the following code causes and EXC_BAD_ACCESS(code=2 address=0xb7ffffcc) error.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
PostCell *cell = (PostCell *)[tableView cellForRowAtIndexPath:indexPath];
CGSize postTextSize;
if (![cell.postText.text isEqualToString:#""]) {
postTextSize = [cell.postText.text sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(cell.postText.frame.size.width, 200)];
} else {
postTextSize = CGSizeMake(cell.postText.frame.size.width, 40);
}
return postTextSize.height + cell.userName.frame.size.height + 10;
}
If I instead have:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 100;
}
It works.
It seems to be crashing on this line: PostCell *cell = (PostCell *)[tableView cellForRowAtIndexPath:indexPath];, and I don't know why. I have tried to build clean and reset the simulator, but neither worked. Any ideas why this is happening?
I have tried to reproduce the problem. It turns out that calling cellForRowAtIndexPath: inside heightForRowAtIndexPath causes heightForRowAtIndexPath to be called recursively. Here is an extract of the stack backtrace after the 3 recursion steps:
frame #0: 0x000042d0 DocInteraction`-[DITableViewController tableView:heightForRowAtIndexPath:] + 48 at DITableViewController.m:262
frame #1: 0x0054f688 UIKit`-[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 3437
frame #2: 0x0055040f UIKit`-[UITableViewRowData(UITableViewRowDataPrivate) _ensureSectionOffsetIsValidForSection:] + 144
frame #3: 0x00551889 UIKit`-[UITableViewRowData numberOfRows] + 137
frame #4: 0x00553dac UIKit`-[UITableViewRowData globalRowsInRect:] + 42
frame #5: 0x003f82eb UIKit`-[UITableView(_UITableViewPrivate) _visibleGlobalRowsInRect:] + 177
frame #6: 0x004001e6 UIKit`-[UITableView cellForRowAtIndexPath:] + 113
frame #7: 0x000042f2 DocInteraction`-[DITableViewController tableView:heightForRowAtIndexPath:] + 82 at DITableViewController.m:262
frame #8: 0x0054f688 UIKit`-[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 3437
frame #9: 0x0055040f UIKit`-[UITableViewRowData(UITableViewRowDataPrivate) _ensureSectionOffsetIsValidForSection:] + 144
frame #10: 0x00551889 UIKit`-[UITableViewRowData numberOfRows] + 137
frame #11: 0x00553dac UIKit`-[UITableViewRowData globalRowsInRect:] + 42
frame #12: 0x003f82eb UIKit`-[UITableView(_UITableViewPrivate) _visibleGlobalRowsInRect:] + 177
frame #13: 0x004001e6 UIKit`-[UITableView cellForRowAtIndexPath:] + 113
frame #14: 0x000042f2 DocInteraction`-[DITableViewController tableView:heightForRowAtIndexPath:] + 82 at DITableViewController.m:262
frame #15: 0x0054f688 UIKit`-[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 3437
frame #16: 0x0055040f UIKit`-[UITableViewRowData(UITableViewRowDataPrivate) _ensureSectionOffsetIsValidForSection:] + 144
frame #17: 0x00551889 UIKit`-[UITableViewRowData numberOfRows] + 137
frame #18: 0x003ff66d UIKit`-[UITableView noteNumberOfRowsChanged] + 119
frame #19: 0x003ff167 UIKit`-[UITableView reloadData] + 764
Finally the program crashes. On my Simulator this happens when the back trace is about 57000 levels deep.
Old answer (not wrong, but does not explain the
EXC_BAD_ACCESS):
The problem is that
[tableView cellForRowAtIndexPath:indexPath]
returns nil for rows that are currently not visible. A table view allocates only so many cells that are required to display the currently visible rows. The cells are reused when you scroll the table view.
But heightForRowAtIndexPath is called for all cells of the table view before any row is displayed.
As a consequence, you should not get the text from the table view cells to compute the height in heightForRowAtIndexPath. You should get the text from your data source instead.
tableView heightForRowAtIndexPath is called before cellForRowAtIndexPath, before a cell is displayed, the height needs to be calculated first.
you should get text from your data source, not from cell
I had a similar problem and came across, just to share what I do now which works well for me
CGFloat mycell_height;
- (void)viewDidLoad
{
[super viewDidLoad];
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"MyCell"];
mycell_height = cell.frame.size.height;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return mycell_height; //assuming all cells are the same
}
Hope this helps anyone looking for this and new to iOS programming like me :-)
In this case you should call UITableViewDataSource method for getting cell for indexPath instead of cellForRowAtIndexPath: method of UITableView because at the moment when tableView: heightForRowAtIndexPath: first time called there are no any cells in tableView.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath *)indexPath {
PostCell *cell = (PostCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath];
CGSize postTextSize;
if (![cell.postText.text isEqualToString:#""]) {
postTextSize = [cell.postText.text sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(cell.postText.frame.size.width, 200)];
} else {
postTextSize = CGSizeMake(cell.postText.frame.size.width, 40);
}
return postTextSize.height + cell.userName.frame.size.height + 10;
}
It works and no causes EXC_BAD_ACCESS exception.
SWIFT:
Use this to know when the tables have loaded:
extension UITableView {
func reloadData(completion: ()->()) {
UIView.animateWithDuration(0, animations: { self.reloadData() })
{ _ in completion() }
}
}
Create a boolean value
var tables_loaded = Bool(false)
Then in viewDidLoad:
tableView.reloadData {
tables_loaded = true
// call functions or other stuff regarding table manipulation.
// tableView.beginUpdates()
// tableView.endUpdates()
}
then in
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if tables_loaded {
let cell = tableView.cellForRowAtIndexPath(indexPath)
// Do your magic here!
}
}

UIViewController purgeMemoryForReason: Crashing on iOS 5

So I've been seeing this crash pretty frequently in Crashlytics, both on iPad and iPad 2, running iOS 5. It looks like it's caused by a memory warning, but the stack trace doesn't reference any of my application code, just iOS frameworks:
0 libobjc.A.dylib objc_msgSend + 15
1 UIKit -[UIViewController purgeMemoryForReason:] + 64
2 Foundation __57-[NSNotificationCenter addObserver: selector: name: object:]_block_invoke_0 + 18
3 CoreFoundation ___CFXNotificationPost_block_invoke_0 + 70
4 CoreFoundation _CFXNotificationPost + 1406
5 Foundation -[NSNotificationCenter postNotificationName: object: userInfo:] + 66
6 Foundation -[NSNotificationCenter postNotificationName: object:] + 30
7 UIKit -[UIApplication _performMemoryWarning] + 80
8 UIKit -[UIApplication _receivedMemoryNotification] + 174
9 libdispatch.dylib _dispatch_source_invoke + 516
10 libdispatch.dylib _dispatch_queue_invoke + 50
11 libdispatch.dylib _dispatch_main_queue_callback_4CF + 156
12 CoreFoundation __CFRunLoopRun + 1268
13 CoreFoundation CFRunLoopRunSpecific + 300
14 CoreFoundation CFRunLoopRunInMode + 104
15 GraphicsServices GSEventRunModal + 156
16 UIKit UIApplicationMain + 1090
17 500px iOS main.m line 12
I've googled high and low but can't find any solutions to this. It looks like this is caused by over-releasing a UIViewController instance, but I'm using ARC, so I don't see how that could be the case.
I'm at a loss of how to even approach this. I can't even tell which UIViewController subclass is causing the issue. I've tried reproducing the problem in the simulator and on the device, but I can't find what causes it. Has anyone seen anything like this or have suggestions on how to approach reproducing the issue?
I think I've solved the issue. I was thinking about it, and the problem isn't the unloading of the UIViewController view, it's the posting of the actual low memory warning notification. There are several instances in my code where I call [[NSNotificationCenter defaultCenter] removeObserver:self]. This is fine in the dealloc method, but there were two instances of this in viewDidUnload methods.
I noticed this when my breakpoint in didReceiveMemory of one of the UIViewController's wasn't getting hit. The code in viewDidUnload was also unregistering self from other, system notifications as well, as detailed here.
I'm not going to mark this as an accepted answer until I verify that the crashes stop with the new update.
UPDATE: I've verified with Crashlytics that the problem has been fixed!
I noticed the exact same stack trace in crashes reported by HockeyApp for devices running on iOS 5.
I never called [[NSNotificationCenter defaultCenter] removeObserver:self] except inside dealloc, so this could not be the cause of the crash.
Here is how I was able reproduce the crash: from MasterViewController I push DetailViewController, then pop it by tapping the back button. Finally, I trigger a memory warning and the crash happens (on iOS 5 only).
It turns out that the DetailViewController instance is not released after being popped because of a retain cycle when using SVPullToRefresh:
#implementation DetailViewController
- (void) viewDidLoad
{
[super viewDidLoad];
[self.scrollView addPullToRefreshWithActionHandler:^{
[self refresh];
}];
}
#end
Since the DetailViewController is not released it’s still registered for memory warning notifications and this is what happens:
frame #0: 0x0004d61b MyApp`-[DetailViewController dealloc](self=0x089a5150, _cmd=0x024d2738) + 27 at DetailViewController.m:103
frame #1: 0x0227ae3d libobjc.A.dylib`_objc_rootRelease + 47
frame #2: 0x0227ae00 libobjc.A.dylib`objc_release + 48
frame #3: 0x0227c047 libobjc.A.dylib`objc_storeStrong + 39
frame #4: 0x0004e44c MyApp`__destroy_helper_block_ + 44 at DetailViewController.m:157
frame #5: 0x029b555d libsystem_sim_blocks.dylib`_Block_release + 166
frame #6: 0x0227ae00 libobjc.A.dylib`objc_release + 48
frame #7: 0x0227c047 libobjc.A.dylib`objc_storeStrong + 39
frame #8: 0x00084c8d MyApp`-[SVPullToRefreshView .cxx_destruct](self=0x08bf3af0, _cmd=0x00000001) + 525 at UIScrollView+SVPullToRefresh.m:121
frame #9: 0x0226630d libobjc.A.dylib`object_cxxDestructFromClass + 104
frame #10: 0x02270fde libobjc.A.dylib`objc_destructInstance + 38
frame #11: 0x02271015 libobjc.A.dylib`object_dispose + 20
frame #12: 0x0247a9a1 CoreFoundation`-[NSObject dealloc] + 97
frame #13: 0x00a8cdc7 UIKit`-[UIView dealloc] + 748
frame #14: 0x0227ae3d libobjc.A.dylib`_objc_rootRelease + 47
frame #15: 0x00a90b73 UIKit`-[UIView(Hierarchy) removeFromSuperview] + 194
frame #16: 0x00a8cc10 UIKit`-[UIView dealloc] + 309
frame #17: 0x00a9d6ff UIKit`-[UIScrollView dealloc] + 405
frame #18: 0x013ab36c Foundation`NSKVODeallocate + 105
frame #19: 0x0227ae3d libobjc.A.dylib`_objc_rootRelease + 47
frame #20: 0x00b21c12 UIKit`-[UIViewController setView:] + 447
frame #21: 0x00b21885 UIKit`-[UIViewController unloadViewForced:] + 117
frame #22: 0x00b2180b UIKit`-[UIViewController unloadViewIfReloadable] + 41
frame #23: 0x00b256ff UIKit`-[UIViewController purgeMemoryForReason:] + 75
frame #24: 0x00b2563b UIKit`-[UIViewController didReceiveMemoryWarning] + 41
frame #25: 0x00b2560d UIKit`-[UIViewController _didReceiveMemoryWarning:] + 33
frame #26: 0x0141ca29 Foundation`__57-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke_0 + 40
frame #27: 0x02443855 CoreFoundation`___CFXNotificationPost_block_invoke_0 + 85
frame #28: 0x02443778 CoreFoundation`_CFXNotificationPost + 1976
frame #29: 0x0136119a Foundation`-[NSNotificationCenter postNotificationName:object:userInfo:] + 98
frame #30: 0x0136db03 Foundation`-[NSNotificationCenter postNotificationName:object:] + 55
frame #31: 0x00a64cf4 UIKit`-[UIApplication _performMemoryWarning] + 91
frame #32: 0x00a64e00 UIKit`-[UIApplication _receivedMemoryNotification] + 180
frame #33: 0x00a64f98 UIKit`__block_global_0 + 36
frame #34: 0x029f1450 libdispatch.dylib`_dispatch_source_invoke + 719
frame #35: 0x029edcc4 libdispatch.dylib`_dispatch_queue_invoke + 66
frame #36: 0x029ee4cf libdispatch.dylib`_dispatch_main_queue_callback_4CF + 295
frame #37: 0x023af803 CoreFoundation`__CFRunLoopRun + 2003
frame #38: 0x023aed84 CoreFoundation`CFRunLoopRunSpecific + 212
frame #39: 0x023aec9b CoreFoundation`CFRunLoopRunInMode + 123
frame #40: 0x038d07d8 GraphicsServices`GSEventRunModal + 190
frame #41: 0x038d088a GraphicsServices`GSEventRun + 103
frame #42: 0x00a5a626 UIKit`UIApplicationMain + 1163
frame #43: 0x00002b82 MyApp`main(argc=1, argv=0xbffff318) + 178 at main.m:15
Or in english: the SVPullToRefreshView instance is released as a result of the view being unloaded. Since the SVPullToRefreshView instance is the last object to hold a reference to the DetailViewController, it is released, then deallocated. But purgeMemoryForReason: was still doing things (i.e. accessing instance variables) with the just deallocated view controller, hence the crash.
Once diagnosed the solution was very simple: just avoid the retain cycle in the first place.
#implementation DetailViewController
- (void) viewDidLoad
{
[super viewDidLoad];
__typeof__(self) __weak weakSelf = self;
[self.scrollView addPullToRefreshWithActionHandler:^{
[weakSelf refresh];
}];
}
#end