Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 918
Ash Furrow Avatar asked Jan 07 '12 22:01

Ash Furrow


2 Answers

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!

like image 169
Ash Furrow Avatar answered Nov 01 '22 23:11

Ash Furrow


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
like image 38
0xced Avatar answered Nov 02 '22 00:11

0xced