dispatch_async call in applicationDidFinishLaunchWithOptions not behaving the way I would expect - iphone

I'm totally new to threading in iOS. I have a tab bar based application with tabs as follows:
Home Screen with buttons that only function to change selectedSegmentIndex
Info listing screen that has a hefty web service call in the init method [self doLoadData]
Two other screens that don't matter for this purpose
I want to go ahead and call that init method before I actually call tabBarController.selectedSegmentIndex = 1. So, I did this in my applicationDidFinishLaunching:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[Constants configureApp];
self.window.rootViewController = self.navigationController;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[[[[tabBarController viewControllers] objectAtIndex:1] topViewController] init ];
[self.window addSubview:tabBarController.view];
[self.window makeKeyAndVisible];
});
The behavior of this is that it does kind of work in that it:
Displays Splash Default.png
Shows a white screen
Finally shows the MainWindow with the tabBarController.
Please help because I know I'm doing it all wrong!

Like bbum said, UIKit is not thread safe. Instead of throwing the init in the background, ask yourself what part is making the init slower and work from there.
Are you loading an image from the web or parsing some file? Those are good examples of things that back be put in the background using Grand Central Dispatch (At least the download part of the image, the displaying should still be done in the main thread).
Instead of wrapping the entire init in a dispatch, try something like this in the init method of the view controller:
dispatch_async(queue, ^{
[self doLoadData]
dispatch_async(dispatch_get_main_queue(), ^{
//Set new data to be displayed
});
});
Be sure, when doing this, that the view looks okay without the data (and loads the data once downloaded gracefully) because it will be displayed before things are done downloading.

You can't arbitrarily dispatch various tasks to queues and have any hope that it'll work.
Unless a class and/or method is explicitly documented as being thread safe, then it is not thread safe.
As well, you must very carefully design your own classes to be thread safe. While queues make this easier to do, it is still rife with sharp edges.
In this case, you are futzing with UIKit objects off of the main queue. This is generally verboten outside of a few specific contexts.
You'll want to read the Concurrency Programming Guide for details.

Related

Threading issue acts differently on ios 7

I am using this Project from github, it is an image picker. I have had to make a very small change since ios7 to make the preview images from your albums show again but with that change now when you leave the picker and come back into it the photos selected (2/5) resets to 0/5 even though I have photos selected. How can I fix this?
The dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0) seems to be taking forever to update the ui even with dispatch_async(dispatch_get_main_queue() to reload the ui inside of it. When I comment out the dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0) the pictures load instantly but other things get broken that depend on the queue.
here is the code snippet with the dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0) I changed with the code i changed commented out
AGIPCAssetsController.m:
- (void)loadAssets
{
[self.assets removeAllObjects];
__ag_weak AGIPCAssetsController *weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
__strong AGIPCAssetsController *strongSelf = weakSelf;
#autoreleasepool {
[strongSelf.assetsGroup enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result == nil)
{
return;
}
AGIPCGridItem *gridItem = [[AGIPCGridItem alloc] initWithImagePickerController:strongSelf.imagePickerController asset:result andDelegate:strongSelf];
if ( strongSelf.imagePickerController.selection != nil &&
[strongSelf.imagePickerController.selection containsObject:result])
{
gridItem.selected = YES;
}
[strongSelf.assets addObject:gridItem];
}];
}
dispatch_async(dispatch_get_main_queue(), ^{
[strongSelf reloadData];
});
});
[strongSelf reloadData];
}
AGIPCGridItem is subclass of UIView. Don't work with UIKit objects on background thread.
Make sure you need the background thread and if you do, put only heavy tasks to background. Creating an UIView should not be that case.
Also, it's not recommended to use PRIORITY_LOW use simple PRIORITY_DEFAULT.
Edit: If you are curious why it did work on iOS 6: That's implementation detail of UIKit. It still was wrong, but somehow did what you expected.
I spent quite a bit of time with this code and I couldn't find a proper solution. Apparently the issue has come up on github, and a user offered a fix:
https://github.com/arturgrigor/AGImagePickerController/issues/19
But apparently he just removed all the blocks running in background, so I suppose that for a large amount of images the performance would be bad.
My hunch is that inside a dispatch_async block runs code that calls some UIKit function,
and thus the behaviour is basically undefined.
For example it seems to me that the setAsset function in AGIPGridItem.m is called inside the dispatch_async you posted. It is calling UImage, and although it's inside a lock, it should be still be executed on the background thread, while all the UIKit code should be executed on the main one.
UITableViewCell load images and reused cells
But even if I wrap the call inside a dispatch_async(dispatch_get_main_queue()...) it doesn't work yet.
It seems that the call [view removeFromSuperview]; in setItems in AGIPGridell.m is responsible somehow, but removing it has the side effect of creating a memory leak (unsurprisingly).
The global dispatch queue is a shared resource. DISPATCH_QUEUE_PRIORITY_LOW tasks run after every other task in the queue at a higher priority has run. If the queue is getting a lot of blocks submitted with a higher priority, your DISPATCH_QUEUE_PRIORITY_LOW task may not run for a very long time!
This is documented in the Concurreny Programming Guide as well as the libdispatch man pages
So, basically, other higher priority tasks are keeping things busy and your low priority task is not getting an opportunity to go.
I highlighted iMartin's answer "AGIPCGridItem is subclass of UIView. Don't work with UIKit objects on background thread." He's got it.
I had a very similar issue when moving iOS6 to 7. I was dispatching an ALAssets request in a background thread. Once the fetch completed, I would construct the UIImageView, a UILabel, and a wrapper and then send this object to the main/foreground thread to be rendered. This worked fine on iOS6, but on 7 it was not draw for about 20 seconds. It would sometime draw after a UI event like a touch.
Fix was to fetch the ALAsset in the background, send that to the main thread where I created the image view, etc. Works like a charm now.

is this code using UIActivityIndicatorView flawed?

Is this code using UIActivityIndicatorView flawed? It appears that I don't actually get to see the indicator/spinner at all here, so is this because the view isn't drawn until the who viewDidLoad completes?
Is the only way around this to do the viewDidLoad custom work (e.g. data updates) on a separate thread? (I was hoping in this case for an easier single-thread operation). Is there a way to force the view to refresh after the "startAnimating" line perhaps prior to the data loading commencment?
Code from UITableViewController implementation:
- (void)viewDidLoad {
// Wait indicator - Start
self.waitView = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
self.waitView.hidesWhenStopped = true;
[self.view addSubview: self.waitView];
// Load data into tableview
[NSThread sleepForTimeInterval: 5.0]; // Test code to simulate
[self.waitView stopAnimating];
}
You should also call startAnimating. Sleeping is not a good idea. I would prefer the performSelector-methods which starts a not recurring NSTimer under the hood.
Try this:
-(void) doStuff:(id)aSender
{
[self.waitView stopAnimating];
}
-(void)viewDidLoad
{
...
[self performSelector:#selector(doStuff:) withObject:self afterDelay:5.0];
}
in addtion: also set the frame- or bounds-property of the ActivityIndicatorView somewhere like sosborn said in his comment
Actually the answer from Thomas should work as it is, I will add a little explanation as to why not use sleep as you have done it.
All the UI processing on iPhone (and most of OSs as well) is being done in only one thread - the main thread, the thread that executes the so called run loop. If you stop that thread the UI will stop, nothing will be drawn.
Putting sleep into viewDidLoad, which runs in the main thread, will do just that - stop UI from doing anything. So because immediately after wakeup you've called [self.waitView stopAnimating] and the activityview should hide when not animating, you can't see it at all - you just didn't give it any time to show.
Thomas used a NSTimer to call stopAnimating after 5 seconds - now this lets the main thread to execute code before stopping animation and hiding waitView and this will work for your test.
Better yet you just let it animate without any timer and use a delegate patter to be informed by the tableView loading code after the data has been loaded, then stop animating. You don't know how long loading of data will last, so it's better to wait until it's finished than stop animating after any specific time.
Oh well, and the size and position, makes sense, but for testing it doesn't matter and is not the cause of not seeing it - if not specified it will be added at 0,0 and have a default size so you will see it anyway.

Why does addSubview load the view asynchronously

I have a UIView that I want to load when the user clicks a button. There happens to be some data processing that happens as well after I call addSubview that involves parsing an XML file retrieved from the web. The problem is the view doesn't show up until after the data processing even if addSuview is called first. I think I'm missing something here, can anyone help?
Code: I have a "Loading..." view I'm adding as a custom modal (meaning I'm not using the modalViewController). This action is linked to a button in the navigationController.
- (IBAction)parseXml:(id)sender {
LoadingModalViewController *loadingModal = [[LoadingModalViewController alloc] initWithNibName:#"LoadingModalViewController" bundle:nil];
[navigationController.view addSubview:loadingModal.view];
[xmlParser parse];
}
Howdy! If you're looking for an easy work around:
[self showLoadingScreen]
[self performSelector:#selector(methodToDoWork) withObject:nil afterDelay:0.3];
However you're better off making methodToDoWork asynchronous if you can.
If you are doing your processing on the main thread, it will block the main thread until its done, which means your UI will become unresponsive and not update until the main thread resumes.
You need to perform your XML processing on a background thread using something like NSOperation or an existing asynchronous API and update your view when you have finished.
Its hard to be of more help and get a better idea of whats going wrong without seeing your code unfortunately.

Recommended thread layer to use for iPhone development?

I'm new to Objective C, and Mac development... It appears that I can use the Posix threads API in my app.. Is this the recommended way? Or is their some Apple API I should be using for mutexes, condition variables and threads instead?
I should add that I'm developing for the iPhone.
I'd like to add exactly what I'm trying to do. Basically, CoreLocation is asynchronous... You tell it to start updating you, and then it just calls an update method on you periodically...
The problem I have is that I need another thread to block until an update occurs... How can I make the main application thread block until at least one CoreLocation update occurs? Is their an NSConditionVariable? :)
I'd suggest an even easier way to get stuck into threads is to use the following call:
[self performSelectorInBackground:(#selector(myMethod)) withObject:nil];
This will automatically create a new background thread for you to run in. Incidentally make sure you do the following inside your background method:
-(void) myMethod {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// code you want to run in background thread;
[pool drain];
}
This is necessary because there isn't a default autorelease pool set up for any threads except the main one.
Finally, talking about blocking the main thread, you could use the following from your background thread to do this:
[self performSelectorOnMainThread:(#selector(myOtherMethod)) withObject:nil waitUntilDone:YES];
The optional third parameter will hold up the main thread for you if you want it to do so.
Hope that helps!
It depends on what you are trying to do, but I would start with NSOperation and NSOperationQueue. It makes it pretty simple to hand off background tasks. Take a look at Dave Dribin's blog post on NSOperation concurrency as well: http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/
Instead of blocking the user interface by making it hang, I would suggest showing some kind of loading screen until you've received your first update. The could would look something like this:
- (void)viewDidLoad {
...
[myCLLocationManager beginUpdates];
[self showLoadingIndicator];
....
}
- (void)locationManager:(CLLocationManager *)manager didReceiveUpdates {
[self hideLoadingIndicator];
// Additionally load the rest of your UI here, if you haven't already
}
Don't quote me on those method calls verbatim, but that's how I suggest solving your problem, in essence.
Yes there is an NSCondition object, and it will probably do exactly what you want for the CoreLocation scenario you mentioned.

How do I create a reusable Loading Screen?

How do I create a loading screen that can be reused at any given time. I'm aware of the Default.png but I need the flexibility to plug in a loading screen at any point during the application life cycle.
This is what I have thus far.
//inside a method that gets called by a UIButton
LoadingViewController* loadController = [[LoadingViewController alloc] initWithNibName:#"Loading" bundle:nil vertical:NO];
[self.view addSubview: loadController.view];
//some method call that takes a few seconds to execute
[self doSomething];
//This loads some other view, my final view
[self.view addSubview: someOtherView]
but it seems that the loading view is never displayed. Instead the previous view stays there until the "someOtherView" gets added. I put trace logs and the code does seem to get executed, I even replaced [self doSomething] with a sleep(2), but the intermediate loading view is never displayed.
If I remove [self.view addSubview:someOtherView]; then after a few seconds...(after doSomething finishes executing) the load view is displayed since there is no view that is pushed on top of it, however this is obviously not the functionality I want.
Can explain this behavior? Is there something about the rendering cycle that I am misunderstanding because it doesn't seem like the view (on the screen at least) is instantly updated, even though I call a [self.view addSubview: loadController.view];
Would I need to create a separate thread?
In general, for changes in the UI to be made visible to the user, control must return to the main runLoop. You are only returning to the runLoop after taking the loading view down and replacing it with the other view. One strategy for dealing with this is to move the code that does the loading onto another thread. NSOperation and NSOperationQueue can be used for this.
An even simpler approach is to use performSelectorInBackground:withObject to do the processing. Once processing is complete the UI can be updated again to show the data. It is important to remember that the UI updates must be carried out on the main thread. Use performSelectorOnMainThread:withObject:waitUntilDone: to accomplish this from the loading thread.
This sounds like a lot of complication but it is really as simple as breaking your single method up into three separate methods as follows:
Display the loading view and start the background process - this is the button action method.
Do the background loading - called from the button action function with performSelectorInBackground:withObject.
Remove the loading view and update the display with the data - called from the background thread with performSelectorOnMainThread:withObject:waitUntilDone.
I created a subclass of UIView where I initialized how my loading-view should work and look like. (My view appeared and slided in from the bottom with an nice animation).
I then added code that handled whether the loading-view should be visible or not in a subclass of UIViewController.
I then let all my viewcontrollers be an subclass of my new viewcontrollerclass which made it possible for me to do:
[self showloadingMessage:#"loading..."];
in all my viewcontrollers...