I would like to use MBProgressHUD the following way: In my method I call multiple servers and other actions that require showing a HUD. I was wondering what would be the best way to "hold" the main thread until those particular actions are done.
For example:
method {
MBProgressHUD (action 1) //don't move on until this is done
//some other code...
MBProgressHUD (action 2)
//some other code...
}
I am pretty new to iOS so sorry if this is too simple. thank you.
I think you use graphical change in MBProgressHUD.
example :-
[HUD showWhileExecuting:#selector(fetchNewData) onTarget:self withObject:nil animated:YES];
fetchNewData will be called on secondary thread . so no use graphical change in MBProgressHUD.
if you want to graphical change use in hudWasHidden delegete
You may take a look at this answer, or ask the developer over at Github. Also, it is never a good idea to block the main-thread. Call it in a different thread. Go for Grand Central Dispatch.
Related
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.
I've got a couple background tasks to process while still keeping the UI responsive. I started down the path of creating and managing threads, but soon came across the NSOperations. Sounds like a better solution . . .
However, I cannot seem to get a reference to the NSOperationQueue. The Threading Programming Guide suggests the following
#implementation MyCustomClass
- (void)launchTaskWithData:(id)data
{
NSInvocationOperation* theOp = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(myTaskMethod:) object:data];
// Add the operation to the internal operation queue managed by the application delegate.
[[MyAppDelegate sharedOperationQueue] addOperation:theOp];
}
// This is the method that does the actual work of the task.
- (void)myTaskMethod:(id)data
{
// Perform the task.
}
. . . but I (and more importantly, the compiler) don't see the 'sharedOperationQueue' message when I implement the code in my app.
What am I missing?? Is sharedOperationQueue deprecated and no longer available? How can I get an NSOperationQueue reference?
sharedOperationQueue is not part of the official API. It is a custom method you are supposed to implement yourself, in this example as a class method in your app delegate class. The method should create and return NSOperationQueue or, if it already has created the queue, simply return the existing one.
How you implement this in your case is up to you. You don't have to follow the example. Simple create an operation queue with alloc/init (no magic here) and store a reference to it in a property so that you can later release it when you no longer need it.
Cocoa Is My Girlfriend has a good tutorial, this will help you to use NSOperationQueue.
You have to declare application delegate before adding operation:
AppDelegateClass * MyAppDelegate = [NSApp delegate];
I know Apple said all UI-related operations should be carried out on main thread.
So my question is "all UI-related operations" really mean every possible UI-related operaton?
For example, I will addSubview ViewB to ViewA in a separate thread. Should I always use [ViewA performSelectorOnMaintThread:#selector(addSubview:)...... on the separate thread?
thanks
Yes, You should deal with all about UI in the main-thread.
If you are under MFC, you should be in the same manner.
Grand Central Dispatch does things in background threads (for example this line: dispatch_async(dispatch_get_main_queue(), myBlock);) and no crash at all. And i do a lot of things on myBlock()
Yes.
It will crash otherwise.
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.
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.