Ok, here is the deal, I hate putting out questions about my debugging and crashes. Because I usually handle them myself, but I just cannot get my way around this, even after viewing multiple questions already.
Ok so here is the problem, I find my app randomly on and off crashing with this stack trace:
*** -[ViewController respondsToSelector:]: message sent to deallocated instance 0x1e5d2ef0
Where ViewController can vary, sometimes the place where my code crashes, has NO relevance to that particular ViewController and doesn't own or call it.
Also, to get that console trace, I have enabled Zombies, otherwise I would get no console print at all, I would only get: objc_msgSend, which I know means I am messaging something that is released. But I cannot find where that is... I am really stuck! Usually I always debug my crashes, so I am really stuck on this.
Again, this crashes in different places at different times, on and off. And the place it crashes has almost no relevance to the ViewController. And I find this very confusing.
Do you need any of my code? I have a lot of files and since it is crashing in different places, distributing my code will be a mess!
I have tried to add symbolic breakpoints with no luck, and Zombies is not available on the Instruments application for iOS. I cannot run my app on the simulator as it has unsupportive architecture frameworks for it.
Thanks everyone...
Use Instruments to track down deallocated instance errors. Profile your application (Cmd ⌘+I) and choose Zombies template. After your application is running, try to crash it. You should get something like that:
Click on the arrow next to address in the popover to show object that was called after it was deallocated.
You should see now every call that has changed retain count of this object. This could be because sending directly retain/release messages as well as draining autorelease pools or inserting into NSArrays.
RefCt column shows retainCount after action was invoked and Responsible Caller shows class name and method in which it was performed. When you double click on any retain/release, instruments will show you line of code where this was performed (If this isn't working, you can examine call by selecting it and choosing its counterpart in Extended Detail pane):
This will let you examine all the retainCount lifecycle of object and probably you'll find your problem right away. All you got to do is find missing retain for latest release.
had a similar problem. In my case a viewController needed to get navigationController events, so it was registering as the navigation controller delegate:
self.navigationController.delegate = self;
The crash occurs when that controller was dealloc'ed but was still the delegate for the view controller. Adding this code in dealloc had no effect:
-(void) dealloc
{
if (self.navigationController.delegate == self)
{
self.navigationController.delegate = nil;
}
because at the point that dealloc is called, the view controller has already been removed from the view hierarchy, so self.navigationController is nil, so the comparison is guaranteed to fail! :-(
The solution was to add this code to detect the VC leaving the view hierarchy just before it actually does so. It uses a method introduced in iOS 5 to determine when the view is being pop'ed and not pushed
-(void) viewWillDisappear:(BOOL) animated
{
[super viewWillDisappear:animated];
if ([self isMovingFromParentViewController])
{
if (self.navigationController.delegate == self)
{
self.navigationController.delegate = nil;
}
}
}
No more crashes!
For anyone who can't solve it, here are some other techniques:
https://stackoverflow.com/a/12264647/539149
https://stackoverflow.com/a/5698635/539149
https://stackoverflow.com/a/9359792/539149
https://stackoverflow.com/a/15270549/539149
https://stackoverflow.com/a/12098735/539149
You can run Instruments in Xcode 5 by clicking the project popup->Edit Scheme...Profile ->Instrument and choose Allocations or Leaks, then profile your app, then stop Instruments, click the info button in Allocations and "Enable NSZombie Detection".
However, for the messages that come directly from the com.apple.main-thread, this probably won't reveal anything.
I banged my head on this for over two hours and the answer turned out to be an over-release, which I discovered by commenting out a copy of my project by brute force until I found the culprit:
[viewController release];
viewController = NULL;
The problem is that release doesn't set the variable to NULL.
That means that setting it to NULL calls release again, decrementing the refcount and freeing the memory immediately until later when the variables that reference viewController are finished with it.
So either enable ARC or make sure your project consistently uses release or NULL but not both. My preference is to use NULL because then there is no chance of referencing a zombie but it makes finding where objects are released more difficult.
I had met the same problem in iOS yesterday. I have made IAP in App "About" subview, and I have added Transaction Observer in "About" viewDidLoad. When I purchase for the first time, no problem, but after I back to main window and enter about subview to purchase again, the problem "message sent to deallocated instance" happened, and the App crashed.
- (void)viewDidLoad
{
[[SKPaymentQueue defaultQueue] addTransactionObserver:self]; object:nil];
}
After I remove Transaction Observer in dealloc, the problem is solved.
- (void)dealloc
{
// Even though we are using ARC, we still need to manually stop observing any
// NSNotificationCenter notifications. Otherwise we could get "zombie" crashes when
// NSNotificationCenter tries to notify us after our -dealloc finished.
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
I had a very similar issue and I figured out it was due to navigation controller delegates set.
The below solved my issue,
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.navigationController.delegate != self) {
self.navigationController.delegate = self;
}
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.navigationController.delegate == self) {
self.navigationController.delegate = nil;
}
}
Had the same problem in OS X.
To solve this not enough - (void)dealloc method as #SoftwareEvolved already said. But unfortunately - (void)viewWillDisappear is available only on version 10.10 and later.
I introduced custom method in my NSViewController subclass where set all the zombie-dangerous references to nil. In my case that was NSTableView properties (delegate and dataSource).
- (void)shutdown
{
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
}
That's all. Each time I'm about to remove view from the superview need call this method.
I had the same Problem.It was difficult to find which delegate cause issue, because it does not indicate any line or code statement So I have try some way, Maybe it becomes helpful to you.
Open xib file and from file's owner, Select "show the connections inspector" right hand side menu. Delegates are listed, set them to nil which are suspected.
(Same as my case)Property Object like Textfield can create issue, So set its delegates to nil.
-(void) viewWillDisappear:(BOOL) animated{
[super viewWillDisappear:animated];
if ([self isMovingFromParentViewController]){
self.countryTextField.delegate = nil;
self.stateTextField.delegate = nil;
}
}
Related
I am building a Mac application. I am adding a childWindowController to mainWindow. In my childWindowController, I have several buttons with their actions connected in IB. But when I press the NSButton, the application crashes and I get EXC_BAD_ACCESS message in the terminal. I also tried to perform setTarget:self, but that doesn't help at all.
Here's my code: applicationDidFinishLaunching
HomeWindowController *home_WindowController = [[[HomeWindowController alloc] initWithWindowNibName:#"HomeWindowController"] autorelease];<br/><br/>
[[self window] addChildWindow:home_WindowController.window
ordered:NSWindowAbove];
And in the HomeWindowController:
- (id)initWithWindowNibName:(NSString *)windowNibName
{
self = [super initWithWindowNibName:windowNibName];
if (self) {
// Initialization code here.
}
return self;
}
- (void)windowDidLoad
{
[super windowDidLoad];
}
-(IBAction)action:(id)sender
{
NSLog(#"------------------ ");
}
What is wrong here? I am binding the NSButton to FileOwner and its action as well. Normally same as for iOS for IB. When I don't bind the IBAction, I don't get EXC_BAD_ACCESS.
It finally got resolved, I was releasing the childWindowController on appDelegate method after adding it on main window.But I dont understand why cant I release it …
Because you own the window controller, and the window controller owns its window. The window isn't keeping its controller alive; you are. And when you're not, it dies out from under anything that might want to talk to it, such as a button that has it as its target.
More generally, trying to shrug off your ownership responsibilities onto other objects—e.g., expecting a window to own its WC for you—is asking for memory-management bugs.
(My only exception to that is indirectly owning objects through collections: if I own, say, an array full of Things, I don't retain and release each Thing individually on its way in and out. Anything else, I expect to outsmart me.)
since it is a local instance of childWindowController on AppDelegate (just for adding it above)?
I don't understand what you meant by that.
Because there are multiple situations in which I would want to pop a view controller from the navigation stack, I have one method that does it and it is called from three different places.
- (void)dismissSelfCon {
NSLog(#"dismiss");
[locationManager stopUpdatingHeading];
[locationManager stopUpdatingLocation];
locationManager.delegate = nil;
mapView.delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[[Trail_TrackerAppDelegate appDelegate] navCon] popViewControllerAnimated:YES];
}
In one situation, if the mapView has an annotation placed on it (I'm not sure if that is the defining characteristic, but I think it is), this method is called (and I am sure that it is called because #"dismiss" is printed to the console), but the location manager does not stop sending location updates! Also, because the delegate is not set to nil, the app crashes because the view controller receives respondsToSelector: from one of the objects of which it is a delegate.
How is this possible?
The most likely cause of this is that locationManager at this point is nil. First rule: always use accessors; don't directly access your ivars except in init and deallloc.
My suspicion from your description would be that this object (the one with dismissSelfCon) doesn't clear locationManager.delegate during dealloc, and that you're being deallocated without calling dismissSelfCon.
The solution was this:
The way I have my view controller set up (which is a little strange I know, and is something I'm trying to change/fix if you will see my question here: Can't allocate CLLocationManager), the CLLocationManager is being allocated, delegate set, etc in viewDidAppear. I present a MFMessageComposeViewController during the app, and when it gets dismissed, viewDidAppear is called again, re-allocating the CLLocationManager and causing my problem. With a little boolean magic, I adjusted the viewDidAppear code so that the CLLocationManager is only set up and allocated one time.
This is by far the weirdest problem I've been stuck with.
I have a UIViewController on a UINavigationController and I want to call a method at viewDidAppear using NSInvocationOperation so it can run on a back thread when the view becomes visible.
The problem is that if I pop the view controller BEFORE the operation (in this case the testMethod method) completes running, the app crashes.
Everything works fine if I pop the view controller AFTER the operation runs it's course.
When the app crashes, it stops at [super dealloc] with "EXC-BAD-ACCESS" and gives me the following error.
bool _WebTryThreadLock(bool), xxxxxxxxx: Tried to obtain the web lock
from a thread other than the main thread or the web thread. This may
be a result of calling to UIKit from a secondary thread. Crashing
now...
And this is my code (super simplified)..
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSInvocationOperation *theOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(testMethod) object:nil];
[operationQueue addOperation:theOperation];
[theOperation release];
}
- (void)testMethod
{
NSLog(#"do some stuff that takes a few seconds to complete");
}
- (void)dealloc
{
[_tableView release];
[super dealloc];
}
The testMethod has some code that takes a few seconds to complete. I only have a few clues and I really don't know how and where to start debugging this.
Clue #1: The funniest thing is that if I remove the [_tableView release]; from dealloc then the app doesn't crash. But of course this would cause a leak and I can't remove it.
Clue #2: I've tested this code on a separate "clean" UIViewController with a UITableView and to my surprise it didn't crash.
Clue #3: The app doesn't crash is the UITableView's datasource is set to nil in viewDidLoad.
Clue #4: The app doesn't seem crash if I use the same code in viewDidAppear somewhere else like an IBAction.
Clue #5: I've tried looking over stack data with NSZombie but it gives me tons of data and it leads me nowhere.
I have some very complicated code within my UITableViewDelegate and UITableViewDataSource and I really don't know where to start debugging this. I really hope I don't have to go through line by line or rewrite the entire thing because of this.
Any pointers on where I should be looking?
The problem is likely that your view controller's last reference is the operation queue holding onto it, which means you are technically calling (or having the system call) some UIKit methods in a background thread (a big no-no) when the operation cleans up.
To prevent this from happening, you need to send a keep-alive message to your controller on the main thread at the end of your operation, by adding something like this to the last line in your testMethod:
[self performSelectorOnMainThread:#selector(description) withObject:nil waitUntilDone:NO];
There is still a chance that this message may get processed before the operation queue releases your view controller, but that chance is pretty remote. If it's still happening, you could do something like this:
[self performSelectorOnMainThread:#selector(keepAlive:)
withObject:[NSNumber numberWithBool:YES]
waitUntilDone:NO];
- (void)keepAlive:(NSNumber *)fromBackground
{
if (fromBackground)
[self performSelector:#selector(keepAlive:) withObject:nil afterDelay:1];
}
By sending a message to your view controller on the main thread, it will keep the object alive (NSObject retains your view controller until the main thread handles the message). It will also keep the view controller alive if you perform a selector after a delay.
You're crashing because the controller is still trying to use your tableView reference and since you poped the viewController, everything will go away in the dealloc and the tableView is still populating itself.
You can try asking in your dealloc method if your operation is still running, so you can cancel it and the everything should be fine.
Once you add an operation to a queue, the operation is out of your
hands. The queue takes over and handles the scheduling of that task.
However, if you decide later that you do not want to execute the
operation after all—because the user pressed a cancel button in a
progress panel or quit the application, for example—you can cancel the
operation to prevent it from consuming CPU time needlessly. You do
this by calling the cancel method of the operation object itself or by
calling the cancelAllOperations method of the NSOperationQueue class.
Cancelling an operation does not immediately force it to stop what it
is doing. Although respecting the value returned by the isCancelled is
expected of all operations, your code must explicitly check the value
returned by this method and abort as needed. The default
implementation of NSOperation does include checks for cancellation.
For example, if you cancel an operation before its start method is
called, the start method exits without starting the task.
I'm building a Cocoa application and have a question about using window controllers. The idea is that if the user selects New from the menu bar, an instance of MyWindowController which is a subclass of NSWindowController is created and a new window from MyWindow.xib is displayed.
I'm handling the action in the application delegate. From what I have seen after searching around something like the following could be done. Once the window is displayed I don't have any reason to store a pointer to the window controller anymore and since I allocated it I also autorelease it before displaying the window.
[[[[MyWindowController alloc] init] autorelease] showWindow:self];
Since the window is released soon afterwards the window will briefly display on the screen and then go away. I have found a solution where I retain the window controller in the -showWindow: method and let it release itself once it gets a windowWillClose notification.
- (IBAction)showWindow:(id)sender
{
[self retain];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification
object:self.window
queue:nil
usingBlock:^(NSNotification *note) {
[self release];
}];
[super showWindow:sender];
}
Is there a better way to do this? I have searched the Apple documentation and have not found anything on which practices to use. It sounds like something very basic which it should cover so maybe I'm just searching with the wrong terms.
Normally you would hold on to the window controller, and only release it when you are done with it. I'd say that your app delegate would be responsible for that. Just store them in an array if there can be multiple. Whilst your solution may work, it's not very elegant.
If you are working on a document based Cocoa app, you create the window controller in your document subclass method makeWindowControllers and let that class hold a pointer to your window controller.
func windowShouldClose(_ sender: NSWindow) -> Bool {
#if DEBUG
let closingCtl = sender.contentViewController!
let closingCtlClass = closingCtl.className
print("\(closingCtlClass) is closing")
#endif
sender.contentViewController = nil // will force deinit.
return true // allow to close.
}
I have a very simple app. 1 navigationController with 2 viewControllers.
The first view has only a button.
The second view has a map view (MKMapView).
I checked show currentlocation property of this map view
I created outlet and then connect everything by using Builder Interface.
In the dealloc method of the second one, I set nil to delegate of mapview, and then release mapview outlet.
mapView.delegate = nil;
[mapview release];
When I tap the button in view1, view2 will be loaded, and then I tap back button. If I do it normally, everything works well. But If I do it very quickly, repeat many times. The app will be crashed.
If I do not release mapView or I do not check showcurrentlocation property, app works well.
I can't figure out why this happen. Anyone helps me solve this problem. Thanks so much !
My fix in controller class was to ..
(void)dealloc
{
mapView.showUserLocation = NO; // Work around bug in MKMapView
[super dealloc];
}
Try launching your app via Instruments with Zombie instrument added. It should show you overreleased object. You'd also should add the Allocations instrument with VM tracker to see how memory consumption goes over time. This way you'd get your answer very quick.
p.s. more code would help better.