iPhone Initial Data Load and 'Please Wait' Screen - iphone

I am updating a rather straightforward iPhone app that requires an initial data load into a SQLite databbase upon initial setup. I am using the DSActivityView class to provide a nice "Please wait" message. The entire data load takes about 15 seconds on WiFi and about 30-45 seconds on 3G.
This whole process originates in the application:didFinishLaunchingWithOptions method of my app delegate.
The data load procedures are being launched in a separate thread by creating an instance of an NSOperation object called UpdateTable for each of the 8 tables. Each operation is placed in an NSOperationQueue, and then released. The data loading works like a champ.
What is NOT working like a champ; however, is the replacement of the "Please wait.." view with the main navigation controller view.
The first approach was to instantiate an NSInvocationOperation that called a method in my app delegate that to execute the following:
- (void) loadNavController;
{
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
[defaultImageView removeFromSuperview];
[DSBezelActivityView removeViewAnimated:YES];
}
This operation was added to the queue after the last UpdateTable operation was added.
The reason this approach is buggy is because the NSInvocationOperation runs concurrent with the other processes in the thread; therefore, the method shown above fires before the last table update can be performed.
So I tried the following approach:
In my UpdateTable.m (where all of the JSON and SQLite is taking place), I entered the following line to execute immediately after the last table update completed:
[appDelegate performSelectorOnMainThread:#selector(loadNavController) withObject:nil waitUntilDone:NO];
This approach got the timing right, however, the main UNavigationController and UIWindow objects were both nil with loadNavController executed. Setting some debug breaks, I noticed that the delloc method of my app delegate was firing prior to the loadNavController method was executed. Both objects are assigned to the MainWindow via IBOutlets that are set up as IBOutlets (nonatomic, retain) in the App Delegate. I haven't a clue as to why the dealloc is firing, as this process is taking less than 10 seconds in total.
Any assistance you might render would be greatly appreciated. Many thanks in advance.
VB

A quick note to update this thread with the solution.
The performSelectorOnMainThread approach turned out to be correct. The reason the navController object was nil when the selector was called was due to me creating a new instance of the app delegate:
AppDelegate *appDelegate = [AppDelegate alloc] init];
instead of referencing the existing object:
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
Works like a charm.

Related

Random crashes occur in my iphone project, see two call stacks, all in web core, all crash after UIWebView deallocate

Random crashes occur in my iphone project, see two call stacks, all in web core, all crash after UIWebView deallocate.
See crash 1, seems web core can't close it completely, is it blocked by other running threads?
See crash 2, why it is still dealing with some issues even if UIWebView has been released?
Any idea or discussion will be appreciated, thanks in advance.
Crash 2 has higher reproducible rate than crash 1, scan WebCore source code, it seems be manifest cache has been deleted (who did it?) when try to access it.
My guess is that you're setting some object (probably your view controller) as the delegate of your webView, either via Interface Builder, or via code that looks something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
self.webView.delegate = self;
}
- (void)dealloc
{
[self.webView release];
[super dealloc];
}
This code has a serious bug that's not immediately obvious if you don't know what to look for. Can you spot it?
When the controller gets dealloc'd it releases the webView, but this doesn't necessarily mean the web view no longer exists. It may still be retained by some code down inside its internal implementation, and it may still be loading content.
When it finishes loading, the web view will call a method like webViewDidFinishLoad: on its delegate. The problem is, your view controller was the delegate, and your view controller no longer exists. The section of memory where your view controller used to live has been reclaimed by the operating system, and is no longer accessible to your application process. The web view doesn't know this though. It still has a reference to that memory address in its delegate property. So it tries to call a delegate method on that object, and whoops... EXC_BAD_ACCESS.
Crashes like this appear to be random since they depend on what your app is doing in the background at the time. This makes them tricky to diagnose. Fortunately they're easy to solve. Simply set the object's delegate to nil before you release it your dealloc statement, like this:
- (void)dealloc
{
self.webView.delegate = nil;
[self.webView release];
[super dealloc];
}
By setting the web view's delegate to nil before you release it, you guarantee that the web view won't try to call delegate methods on your object after it's been deallocated.
If you're using ARC, your class may not have a dealloc method. That's normally fine, but in a case like this you should go ahead and add one. You don't want to call release if you're using ARC, but you should still add a dealloc method and set the delegate property to nil.
To avoid similar crashes in the future, apply this best practice any time you set your object as the delegate of another object.
Before releasing the webview, try to reset the delegate an stop loading.
[webView stopLoading];
webView.delegate = nil;
[webView release];
webView = nil;

iPhone app crashes when user exits UITableView with MBProgressHUD running in separate thread

In my app, I have a UITableViewController which loads calculated data from my Core Data store. This can take quite some time and hangs the main thread, so to give the user visual feedback, I have installed the MBProgressHUD widget which will show a progress indicator (just the spinning wheel) while the calculation method runs in a separate thread.
This is fine, except that the user can still exit the UITableViewController if they think it's taking too long. Of course, this is a good thing, except that when the separate thread concludes its operation, it still tries to call its delegate (the UITableViewController) to hide the MBProgressHUD. This causes a crash because since the user already exited the UITableViewController, it has dealloc'd and release'd itself.
MBProgressHUD has the following code to try to stop this:
if(delegate != nil && [delegate conformsToProtocol:#protocol(MBProgressHUDDelegate)]) {
if([delegate respondsToSelector:#selector(hudWasHidden)]) {
[delegate performSelector:#selector(hudWasHidden)];
}
}
However, my app somehow seems to still be running this inner code ([delegate performSelector:#selector(hudWasHidden)]) even though the UITableViewController is totally gone--causing the app to crash.
Any suggestions? I am not running with NSZombiesEnabled.
From your UITableViewController viewWillDisappear, viewDidDisappear or dealloc method, set the MBProgressHUD.delegate = nil;
Once the user has exited the table controller, the delegate property of the hud points to a deallocated object (= a memory zone that could contain anything). That causes the crash when the computing thread ends and tries to send any message to the delegate.
In your table view controller dealloc, you must set the hud delegate to nil.

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().

crash when switching between tableviews using tab controller and MBProgressHUD

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...

Why does Core Data initialization fail when I attempt to do it at these points?

I see how to solve the problem but it bothers me that I don't understand why this doesn't work. I have a UIViewController subclass that uses Core Data, so it needs the NSManagedObjectContext. The controller is loaded from a nib file where it's placed under a navigation controller which is inside a tab controller.
I tried doing this in initWithCoder and viewDidLoad and for some reason it doesn't work:
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = [[appDelegate managedObjectContext] retain];
For some reason managedObjectContext returns nil and I get this when I try to create a managed object later:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+entityForName: could not locate an entity named 'LogRecord' in this model.'
Which is what you get when your context is nil or the model can't be loaded (or really lacks the entity).
If I do the exact same thing at the top of my saveLogEntry method (which creates managed objects and saves the context) then it works just fine.
If I do what the Recipes sample application does:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
loggingViewController.managedObjectContext = self.managedObjectContext;
// Standard stuff
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
}
(loggingViewController is an IBOutlet in the app delegate).
Does anyone know what specifically might be going on here? It seems like it fails if done "too early" but especially with viewDidLoad I'd expect it to work since I think that occurs after addSubview is called.
Do exactly what the recipes app does.
If you try it in initWithCoder, you don't know if the app delegate has finished initialization (which it hasn't)
If you try it viewDidLoad, you have a similar problem.
That is why you should NOT be accessing the app delegate like so:
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = [[appDelegate managedObjectContext] retain];
This is bad form. It introduces coupling into your design. Use dependency injection, just like the example. It makes your app more flexible.
Because from the app delegate you know exactly what initialization has been performed and can pass in the context at the appropriate time.
Update:
The issue is that your View Controller instance is likely being instantiated in the Mainwindow.xib. Mainwindow.xib (and any other nibs it references) is "defrosted" before the app delegate receives UIApplicationDidFinishLaunchingNotification notification.
The order in which objects are defrosted from nibs is not guaranteed. When initWithCoder: is called on your View Controller you have no idea what other objects have been defrosted from the nib. You also can't be sure whether the app delegate has received the UIApplicationDidFinishLaunchingNotification notification.
It is similar for viewDidLoad. In viewDidLoad, you can be sure that all other objects in the nib have been properly defrosted and initialized, but since the configuration of the app delegate happens outside of the nib file, you can't be sure whether it is safe to call the app delegate.
It is better just to have the app delegate pass in the context when it is "good and ready", preferably in the applicationDidFinishLaunching: method.
Hope that is a little clearer, you should take a look at the iphone programming guide:
http://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/index.html
To glean a better explanation of the iPhone application life cycle.
Hope that helps.
One More Update:
In depth discussion of the iphone launch sequence:
http://www.bit-101.com/blog/?p=2159