crash when switching between tableviews using tab controller and MBProgressHUD - iphone

I have two tableviews. One loads when I select one tab, and the other loads when I select the other tab.
I use MBProgressHUD to allow for quick switching between the tabs as I pull the tableview datasource from the web using Objective Resource and that can take a little bit. So I throw up a HUD progress indicator waiting for the data to load.
This in turn has allowed me to quickly switch between tabs. But.... If I do it quick enough an exception occurs
EXC BAD ACCESS
with NSZombiesEnabled I get this:
2010-08-02 22:44:43.116 Film Fest[962:8703] *** -[MBProgressHUD release]: message sent to deallocated instance 0x85490b0
In both my tableviews I use two different custom tableviewcells.
What should be my next step to debug this?
... so I have moved the code to create the HUD from my viewWillAppear: method to viewDidLoad: and the crash went away.
// The hud will dispable all input on the view (use the higest view possible in the view hierarchy)
HUD = [[MBProgressHUD alloc] initWithView:self.view];
//HUD.graceTime = 0.5;
//HUD.minShowTime = 5.0;
// Add HUD to screen
[self.view addSubview:HUD];
// Register for HUD callbacks so we can remove it from the window at the right time
HUD.delegate = self;
// Show the HUD while the provided method executes in a new thread
[HUD showWhileExecuting:#selector(loadFilms) onTarget:self withObject:nil animated:YES];
however this doesn't really fix my issue as viewDidLoad only occurs once in a long while especially with the new multitasking. I need this HUD to fire the selector everytime the tableview appears.
Why is it not correct for me to have it occur in the viewWillAppear... is it because that can be loaded so much and I just kept on allocating the object? perhasp I should allocate in viewDidload and fire the
// Show the HUD while the provided method executes in a new thread
[HUD showWhileExecuting:#selector(loadFilms) onTarget:self withObject:nil animated:YES];
only in my viewWillAppear:?
Thoughts?

Find all the places where you init/retain/release/autorelease an instance of MBProgressHUD.
The NSZombie tells that this project receives a release message after it is being already deallocated (retain count is zero).
It will help if you will post some code - otherwise it is too difficult to help you...
EDIT:
As you have mentioned in your edit, it would be much better if you'd initiate the HUD only once per view controller (e.g. in viewDidLoad) and call to showWhileExecuting each time you need it (e.g. in viewWillAppear).
But it still doesn't explain the crash.
You you execute the entire code you have posted (as is) for a few times from the same instance of a view controller then you should have memory leaks. That is because I don't see where you release the old instance of HUD.
In addition, you are better set the delegate of the HUD to be nil right before releasing it and before calling the [super dealloc]; in the dealloc method of the view controller.
The last one might cause the EXC BAD ACCESS error.
One more thing, if the entire code you have posted is executed more than once then you should also have few HUD views under the self.view.
I don't know what HUD does in the background - maybe this causes the error...

Related

How to dispose of old controller in a transition between two views?

I'm trying to create a transition between two views by using their controllers. Problem is, how to dispose of the old controller once the transition animation is finished? I can't dispose of it before the animation is finished as that leads to a segmentation fault - apparently the animations involved do not increment the retain count on the views they work with.
Only thing I can think of is to provide a method to be called once the animation is finished to release the controller argument. I am hoping someone will have a more elegant solution.
Here is my sample code for a method in my root view controller:
-(void) switchToController:(UIViewController *) controller {
[controller viewWillAppear:YES];
if (self.currentScreenController != nil) {
[self.currentScreenController viewWillDisappear:YES];
[[self.currentScreenController view] removeFromSuperview];
}
[self.view addSubview:[controller view]];
if (self.currentScreenController != nil) {
[self.currentScreenController viewDidDisappear:YES];
}
self.currentScreenController = controller;
[controller viewDidAppear:YES];
//[controller release]; // <- releasing now will cause errors as subsequent animations reference this controller's view
...
// animations start now
[Edit]
Well, I can't seem to be allowed to release the controller even after the animation is finished for some unexplained reason. I schedule an NSTimer call a few seconds after
the animation completes, I check that the retain count is 1 before releasing, and then
when I release it I get a crash. Here is the method that is called by the timer to release
already unused controllers:
- (void) releaseOldController {
#synchronized(arrayOfOldControllers) {
if (2 < [arrayOfOldControllers count]) {
NSObject * object = [arrayOfOldControllers objectAtIndex:0];
[arrayOfOldControllers removeObjectAtIndex:0];
NSLog(#"releasing object (%#) with retain count(%d)", object, [object retainCount]);
[object release];
}
}
}
Perhaps my strategy is wrong. What I am trying to accomplish is to simulate book page
turning. The book has more than 30 pages, I want to dispose of the old page as soon as
the transition to the new page is finished to release memory. So when the user turns to
a new page, a new controller with a new view is created and added to the root controller
with a fade animation transition of about 1/2 sec. As soon as the animation is finished
I want to release the previous page view as only the new page view is visible. This way
the root controller view has at most 2 subviews at any one time, and usually only 1 -- the current page viewed, after the transition is complete. This should not be so hard to do, but I don't understand why I am getting an error when releasing a controller when it's no longer in use.
[Update] This question is wrong -- the problem is somewhere else and has nothing to do with controller or view. The code above is correct.
Providing an animation finished callback method will certainly work, but it's a little annoying.
A nicer technique available to you: if you're targetting iOS4 and onwards only, you can use animation blocks -- please see iPhone UIView Animation Best Practice -- in particular, the completion: bit allows you to specify what happens at the end of the animation, without having to define a new callback method in the usual way.
If you are trying to simulate the page turning I would load all my controllers in to NSMutableArray and use it as my page guide. You can release all the UIViewControllers after adding them to the array, and then you will be able still have access to them whenever you want to go back.

Why is there a delay when presenting this modal view?

I have an app that starts with a tableview (from a xib) that loads multiple navigation controllers. I want to present a modal view on startup if a long init sequence is underway. I tried presenting a modal view in the App Delegate, but the view doesn't appear until well after the underlying code is already complete.
MainWindow.xib loads TableViewController, so I put my call to presentmodalview in that View Will Appear with the same result. I have numerous NSLOG calls so I can watch what is happening but I can't figure out why the view doesn't appear until after both the app delegate and the tableview controller's viewWillAppear finish. I moved the call to viewDidAppear with the same result. Here is a code snippet:
App Delegate:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Add the tab bar controller's current view as a subview of the window
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
[[UIApplication sharedApplication] setStatusBarHidden:NO];
// if new version being installed do init stuff
if ( <needs update code here>) {
Uncompress *uncompressView = [[Uncompress alloc] initWithNibName:#"Uncompress" bundle:nil];
[self.tabBarController presentModalViewController:uncompressView animated:NO];
[uncompressView release];
}
}
I also tried changing presentmodalviewcontroller to [window addSubview:uncompressView.view] without any luck.
The Update code runs just fine, the problem is the view doesn't appear until both the AppDelegate and the TableView are finished. I'm not creating any views programatically - all are from Xib's. I can't figure out where to call the update function to get the view to appear right away. Any help appreciated. Thank you!
On iOS, the UI is only updated when your code returns control to the run loop. So if your uncompress task takes a lot of time, the UI will only be updated after it has finished.
You can sort of work around this issue by putting the time-intensive task in a separate method and calling that method with [self performSelector:... afterDelay:0.0 ...]; but it is not a good solution because even if the UI will update, user interaction with your app will still be blocked as long as your code blocks the main thread. And if your code takes more than a few seconds to run (e.g. because it runs on an older, slower device), the OS watchdog timer will kill your app.
The best solution would be to put the time-intensive task in a background thread, e.g. with NSOperation or dispatch_async().

"[CALayer release]: message sent to deallocated instance" when dismissing modal view controller

I've been struggling with this for last few days and I cannot find any solution, so I ask you for advice.
I have two UIViewControllers: NewPostUIViewController and SettingsUIViewController. In the second one I have a field:
id<SettingsUIViewControllerDelegate> delegate
and the first one implements protocol
SettingsUIViewControllerDelegate
When a button is pressed the following code is executed in NewPostUIViewController:
SettingsUIViewController *settingsUIViewController = [[SettingsUIViewController alloc] initWithNibName:#"SettingsView" bundle:nil];
settingsUIViewController.title = NSLocalizedString(#"Settings", #"Settings view title");
settingsUIViewController.delegate = self;
[self presentModalViewController:settingsUIViewController animated:YES];
[settingsUIViewController release];
when I want to dismiss SettingsUIViewController I call (code in SettingsUIViewController):
[delegate settingsAreDone:sender];
and settingsAreDone looks following (code in NewPostUIViewController):
[self dismissModalViewControllerAnimated:YES];
This all concludes in:
[CALayer release]: message sent to deallocated instance 0x5a76840
I tried to debug the code by setting a breakpoint in the release methods of both view controllers, but these methods are called so often that it's hard to say what can be the cause of this problem.
Any ideas?
First, the error you're getting isn't indicating that -release is being sent to a view controller, so breakpoints in your view controllers won't help. The over-release is happening on a CALayer, which is likely part of the modal animation.
First, we start with some basics about the delegate. I don't feel great about this being the cause, but you should always start with the easy basics. Your SettingsUIViewController delegate property should be assign, not retain, so you avoid retain loops. That's probably correct already, but when it's not, you can wind up with cases where objects exist longer than you expect them to (and so can send messages after their targets have gone away). Again, probably not the issue, but easy to check and easy to fix.
Next, you should look at the stack trace at the crash. Who is calling [CALayer release]? A possible cause is that the owning view controller gets released before the animation stops. When you close the settings controller, do you immediately close the NewPost controller?

viewDidAppear called twice on the same instance, but only the first time this class loads form NIB

I have a navigation controller. One of the views adds custom subviews in its viewDidAppear:. I notice that the first time I navigate to an instance of this view controller after launching the app, viewDidAppear: invokes twice. If I pop this view off the stack and navigate to it again, viewDidAppear: invokes only once per appearance. All subsequent appearances invoke viewDidAppear: once.
The problem for me is that the first time I get to this view I end up with twice the number of subviews. I work around this problem by introducing a flag variable or some such, but I'd like to understand what is happening and how come I get two invocations in these circumstances.
You should never rely on -viewWillAppear:/-viewDidAppear: being called appropriately balanced with the disappear variants. While the system view controllers will do the best they can to always bracket the calls properly, I don't know if they ever guarantee it, and certainly when using custom view controllers you can find situations where these can be called multiple times.
In short, your -viewWillAppear:/-viewDidAppear: methods should be idempotent, meaning if -viewDidAppear: is called twice in a row on your controller, it should behave properly. If you want to load custom views, you may want to do that in -viewDidLoad instead and then simply put the on-screen (if they aren't already) in -viewDidAppear:.
You could also put a breakpoint in your -viewDidAppear: method to see why it's being called twice the first time it shows up.
maybe you invoke viewDidAppear in viewDidLoad (or some other stuff is going on there), since it's invoked only once during loading the view from the memory. It would match, that it's invoked two times only the first time.
This was not an iOS 5 bug, but a hidden behavior of addChildViewController:. I should file a radar for lack of documentation, I think
https://github.com/defagos/CoconutKit/issues/4
If you have a line like this in your AppDelegate
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
make sure you DON'T have a "Main nib file base name" property in your plist set to "Window.xib" or whatever your custom window nib is named. If you do, remove that row from your plist and make sure you something like
yourRootVC = [[UIViewController alloc] init];
[window setRootViewController:yourRootVC];
in your AppDelegate after instantiating your window. In most cases, you could then safely delete the Window.xib as well.
You definitely should provide more info.
Is this the root view controller?
Maybe you initiate the navigation controller with this root view controller and then push it to the navigation controller once again?
Another solution that may have been your underlying cause: Be sure you are not presenting any new view controllers from within your viewWillAppear: method.
I was calling:
[appDel.window.rootViewController presentViewController:login animated:YES completion:nil];
from within viewWillAppear and seeing my originating VC's viewDidAppear: method called twice successively with the same stack trace as you mention. And no intermediary call to viewDidDisappear:
Moving presentViewController: to the originating VC's viewDidAppear: method cleared up the double-call issue, and so now the flow is:
Original viewDidAppear: called
Call presentViewController here
Original viewDidDisappear: called
New view is presented and no longer gives me a warning about "unbalanced VC display"
Fixed with help from this answer: https://stackoverflow.com/a/13315360/1143123 while trying to resolve "Unbalanced calls to begin/end appearance transitions for ..."
it's such an annoying problem, you'd think it runs once but then I now found out about this which is causing mayhem... This applies to all 3 (ViewDidAppear, ViewDidLoad, and ViewWillAppear), I am getting this when integrating with a payment terminal; once it finish calling the API, the window is being re-loaded when it's already on-screen and all it's memory is still there (not retained).
I resolved it by doing the following to all the routines mentioned above, below is a sample to one of them:
BOOL viewDidLoadProcessed = false;
-(void)viewDidLoad:(BOOL)animated
{
if (!viewDidLoadProcessed)
{
viewDidLoadProcessed = YES;
.
.
.
... do stuff here...
.
.
}
}
Repeat the above for all the other two, this prevents it from running twice. This never occurred before Steve Jobs died !!!
Kind Regards
Heider Sati
Adding [super viewDidAppear:animated]; worked for me:
//Called twice
- (void)viewDidAppear:(BOOL)animated{
}
//Called once
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}

how to know whether dealloc is getting called or not in iphone sdk?

i am using UIviewcontroller subclasses. In my main view i have 3 buttons, each button will load a different nib. and each new nib is having one back button to come back to main view.
when i click one the back button of any view to move to the main view the dealloc of that view is not getting called? i didnt understood this.
can anyone explain when those views dealloc will be called?
if the dealloc method hasn't been called, it means that your retained your viewController object by hands. for example, in this case dealloc will not be called after clicking back button to return
MyViewController *controller = [[MyViewController alloc] init];
[self.navigationController pushViewController:controller animated:YES];
You should add
[controller release];
to this code to be sure that your instance of viewController will be deallocated. If you are absolutely sure, that you had sent equal number of alloc(or any message that increases object's retainCount) and release messages for your object and dealloc method doesn't be called anyway, it will be more complex. I hope that this answer will help. If you will find that your situation is "more complex", post a comment, then I'll try to explain with more details.
I too would like to dive deeper into understanding memory management details (below surface level) where it comes to controllers being pushed on and off of the stack. I built my framework from the text, "Beginning iPhone 3 Development" by Mark and LaMarche, but that text effectively re-uses sub-controllers and their dealloc methods never get called.
I have noticed that repeated use of a sub-controller with a NIB containing a UIWebView that calls Google's web directions url ... eventually results in a memory warning and my data is lost. This involves repeated "reuse" of the sub-controller.
If you can point me as well to in depth text that goes into nav controller and sub view memory management, that would be excellent.