Updating UIAlertView with progress of background thread - objective-c

XCode 4.5, iPad development, iOS6
Hi, I hope you can help a novice developer! Apologies in advance if this has already been answered but I could not find during my searches!
I am developing an app that needs to import a large amount of data into Core Data. The import routine works fine (alert shows 'Please wait' with activity monitor while routine works in the background) but I want to give the users more detailed feedback on the progress of the import (such as 'XX% imported'). The following code kicks the process off and -
- (IBAction)import:(id)sender{
[self showWaiting];
[self performSelectorInBackground:(#selector(callGrouper)) withObject:nil];
}
-(void)showWaiting{
alertMsg = #"Please Wait....";
waitAlert = [[UIAlertView alloc] initWithTitle:alertMsg message:nil delegate:self cancelButtonTitle:nil otherButtonTitles: nil];
[waitAlert show];
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
indicator.center = CGPointMake(waitAlert.bounds.size.width / 2, waitAlert.bounds.size.height - 50);
[indicator startAnimating];
[waitAlert addSubview:indicator];
}
-(void)callGrouper{
ImportRoutine *firstTest = [[ImportRoutine alloc] init];
[firstTest runImport:managedObjectContext];
[waitAlert dismissWithClickedButtonIndex:0 animated:TRUE];
UIAlertView *alert = [[UIAlertView alloc]initWithTitle: #"iPad Application"
message: #"Import complete!"
delegate: self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
}
Within ImportRoutine (separate class) I have code that gathers data on percentage imported but how can I pass this message back to the main thread so I can update 'alertMsg' and in turn update the UIAlertView?

You can dispatch blocks of code back onto the main thread using GCD (grand central dispatch):
dispatch_async(dispatch_get_main_queue(), ^{
// code here to update UI
});
Any object in the scope of the method that contains the dispatch call gets retained which makes it easy to pass objects back into the main thread without worrying about the background thread being deallocated along with its objects before you've had a chance to process the data. Primitive values in the local scope (aka int, float, double, etc) are copied, so if you set an int to 5, dispatch a block where you print the value of the int, and then right after set the int to 10, even if the block executes after you set the int to 10 it'll still print 5. Note that You can't mutate the same mutable object (such as `NSMutableArray or NSMutableDictionary) in two threads at the same time or mutate in one and enumerate in another without crashing so you'll want to be careful about doing something like that (thanks goes to #andrewmadsen for reminding me to warn you).
dispatch_async(), unlike dispatch_sync(), will not wait for the code that's dispatched to complete before continuing execution which is nice since your background thread doesn't need to care if things in the UI have finished.
You could stick the dispatch call inside of the method on the ImportRoutine class that calculates the progress as long as your UIAlertView is addressable outside of your view controller class. Or if you want to follow model-view-controller design principals more closely, you could create a method like so in your view controller:
- (void)updateProgressToPercentComplete:(double)percent {
if ([NSThread currentThread] != [NSThread mainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
// update code or call to method that is guaranteed to be on the main thread.
}
}
else {
// update code or call to method that is guaranteed to be on the main thread.
}
}
If you've gone into the documentation and now you're all like "oh my gosh Objective-C blocks are the coolest thing ever" you could modify the method above so you don't need to write the same update code twice:
- (void)updateProgressToPercentComplete:(double)percent {
void (^updateProgressBlock)(void) = ^{
// update code
};
if ([NSThread currentThread] != [NSThread mainThread]) {
dispatch_async(dispatch_get_main_queue(), updateProgressBlock());
}
else {
updateProgressBlock();
}
}
By the way I noticed in your -callGrouper code that you're using an existing managedObjectContext that I assume you created on the main thread in a background thread... most of core data isn't threadsafe so you need to be extremely careful or you will crash all over the place. You might be better off creating a secondary managed object context on the background thread and then merging changes into the context on the main thread (or save on the background thread and re-fetch on the main thread).
Edit:
Basic flow: Begin your background process from your view controller and pass in a progress block. -> Import class in the background thread executes your progress block periodically -> Inside your progress block you dispatch back to the main thread to update UI.
In your ImportRoutine class add a property declaration like so:
#property (nonatomic, strong) void (^progressBlock)(NSUInteger);
Which means a property called progressBlock that takes an unsigned integer (0-100) and doesn't return anything (void). You should make this property private by using a class extension.
Then you'll want to create a method in your import class like so:
- (void)callGrouper:(void (^)(NSUInteger))progress {
[self setProgressBlock:progress];
// Your import code
}
In your method where you receive progress updates, call the progressBlock and pass in your progress as a number between 0 and 100:
if ([self progressBlock] != nil) {
[self progressBlock](progressValue);
}
Notice that I check to make sure the progress block isn't nil. You would crash and burn if you tried to execute a NULL block.
Then you can pass in a block as the object in your import routine call you already have in the view controller and inside the block dispatch back to the main queue and update your progress.

You can use:
[self performSelectorOnMainThread:#selector(yourSelector) withObject:anObjectIfYouNeedToSendOne waitUntilDone:YES/NO];
The UI runs on main thread and so you cand acces again your UIAlertView or other UI object.

Related

How to switch between background and main threads

I've never used background threads before. I have a time consuming computation currently running on the main thread which appends the data output to a TERecord. My workflow essentially goes:
run long process…
update GUI…
run long process…
update GUI…
and so on.
At several places where the code produces (string) output I update the UI by calling my 'addToRecord' method shown here:
-(void)addToRecord:(NSString*)passedStr:(BOOL)updateUI
{
NSRange endRange;
// add the passed text...
endRange.location = [[theOutputView textStorage] length];
endRange.length = 0;
[theOutputView replaceCharactersInRange:endRange withString:passedStr];
if(updateUI) // immediate GUI update needed...
{
// scroll window contents to BOTTOM of page...
endRange = NSMakeRange([[theOutputView string] length],0);
[theOutputView scrollRangeToVisible:endRange];
[theOutputView display];
}
}
While it does the job, my entire UI remains unresponsive until the process completes, of course. I know I should be doing the heavy lifting on a background thread which I've never used before. I've figured out part of the problem in creating a background thread like below:
-(IBAction)readUserInput:(id)sender
{
// irrelevant code snipped for brevity
if([self checkForErrors] == NO)
{
[runButton setEnabled:NO];
[self performSelectorInBackground:#selector(runWorkThread) withObject:nil];
}
}
-(void)runWorkThread
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
[self runLongProcess];
[pool drain];
}
but i just don't understand how to call the main thread every time the code encounters my 'addToRecord' method, then how to return control to the background thread?
Another possibility might be to remove the updateUI code from my 'addToRecord' method and just have have the main thread calling this code every second or so on a timer?
Any advice and sample code would be greatly appreciated. Thanks!
Instead of using performSelectorInBackground you can use the Dispatch framework (also called GCD), which is the preferred way of handling concurrent work. The Dispatch already has a pool of background threads set up that you can use. To switch thread you call dispatch_async() like this:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
// :
// Do your background work here
// :
dispatch_async(dispatch_get_main_queue(), ^{
// :
// Now you are back in the main thread
// :
});
});
The first parameter is the queue identifier which is supplied to you by either dispatch_get_global_queue() which returns one of the "worker" queues, or dispatch_get_main_queue() which returns the main queue. The last parameter is a code block that is executed on the selected queue.
When requesting a concurrent queue using dispatch_get_global_queue() you specify a Quality of Service, which determines the priority your code will have in relation to other work. See the documentation for more information and possible values.
Read more on the Dispatch

iOS: Core Data class method

Is it possible and practical to create a Core Data class method that will return the current instance of managedObjectContext? I am wondering so that I can segue to other controllers and load modal views without having to pass the managedObjectContext.
Also if I am using Core Data with dispatch_async I know I need to create my own instance of managedObjectContext but I can use the same coordinator. Will this make the information accessible both inside the dispatch_async and in the main thread?
I am basically using the dispatch_async to get data from the API and store it while the user is using the application.
In the past, I've created a Core Data manager singleton class that has simplified things. Here is an example, but this is pre-iOS5/ARC, so some changes need to be made.
I had a similar issue when trying to asynchronously getting data from my server to the app. My method is a bit different, but basically here it is (this is a 4.3 project, so no ARC):
The following methods are in my DataUpdater singleton. This first method is called at app startup:
- (void) update { //download the updates on a new thread
[NSThread detachNewThreadSelector:#selector(updateThread)
toTarget:self withObject:nil];
}
It initializes a thread with this selector, which is responsible only for downloading the content from the API, then passing it back to the main thread to be saved.
- (void) updateThread { //the actual update thread
//New thread, new auto-release pool
//(dunno if you need to do anything fancy for ARC)
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
//...
//YOUR CODE TO DOWNLOAD (BUT *NOT* SAVE) DATA FROM THE SERVER
//DON'T CREATE ANY MANAGED OBJECTS HERE
//...
//Pass the data to the main thread to perform
//the commit to the Core Data Model
[self performSelectorOnMainThread:#selector(saveUpdate:)
withObject:data waitUntilDone:NO];
//kill the thread & the auto-release pool
[NSThread exit];
[pool release];
}
Now that we're back on the main thread, the data is added to the Core Data Model and then the context is saved.
- (void) saveUpdate:(NSArray *) data {
//add the objects to your Core Data Model
//and save context
NSError * error = nil;
[[[CoreManager defaultCoreManager] CoreContext] save:&error];
if (error) {
[NSException raise:#"Unable to save data update"
format:#"Reason: %#", [error localizedDescription]];
} else {
[[NSNotificationCenter defaultCenter] postNotification:
[NSNotification notificationWithName:#"DONE" object:nil]];
}
}
Dealing with the first part of the question only (you shouldnt really ask multiple questions!) you don't have to pass the managed object context around - presumably you are passing a managed object? In that case the context is available as a property of the managed object itself - .managedObjectContext.

How to update UI in a task completion block?

In my application, I let a progress indicator starts animation before I send a HTTP request.
The completion handler is defined in a block. After I get the response data, I hide the progress indicator from inside the block. My question is, as I know, UI updates must be performed in the main thread. How can I make sure it?
If I define a method in the window controller which updates UI, and let the block calls the method instead of updating UI directly, is it a solution?
Also, if your app targets iOS >= 4 you can use Grand Central Dispatch:
dispatch_async(dispatch_get_main_queue(), ^{
// This block will be executed asynchronously on the main thread.
});
This is useful when your custom logic cannot easily be expressed with the single selector and object arguments that the performSelect… methods take.
To execute a block synchronously, use dispatch_sync() – but make sure you’re not currently executing on the main queue or GCD will deadlock.
__block NSInteger alertResult; // The __block modifier makes alertResult writable
// from a referencing block.
void (^ getResponse)() = ^{
NSAlert *alert = …;
alertResult = [NSAlert runModal];
};
if ([NSThread isMainThread]) {
// We're currently executing on the main thread.
// We can execute the block directly.
getResponse();
} else {
dispatch_sync(dispatch_get_main_queue(), getResponse);
}
// Check the user response.
if (alertResult == …) {
…
}
You probably misunderstood something. Using blocks doesn't mean that your code is running in a background thread. There are many plugins that work asynchronously (in another thread) and use blocks.
There are a few options to solve your problem.
You can check if your code is running in the main thread my using [NSThread isMainThread]. That helps you to make sure that you're not in the background.
You can also perform actions in the main or background by using performSelectorInMainThread:SEL or performSelectorInBackground:SEL.
The app immediately crashes when you're trying to call the UI from a bakcground thread so it's quite easy to find a bug.

iPhone SDK: How to know when background task has completed?

We are trying to get a background task working for the purpose of including an activity indicator in a workhouse screen. From our understanding, this requires one to create a background thread to run it on. I also understand that no GUI updates can be performed on the background thread.
Given that, here is the general pattern of what needs to happen.
a.) Pre-validate fields. Make sure user did not enter any invalid data
b.) Setup background task.
c.) Process results from background task
This is what it looks like in code so far:
-(IBAction)launchtask:(id)sender
{
//validate fields
[self validateFields];
/* Operation Queue init (autorelease) */
NSOperationQueue *queue = [NSOperationQueue new];
/* Create our NSInvocationOperation to call loadDataWithOperation, passing in nil */
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(backgroundTask)
object:nil];
/* Add the operation to the queue */
[queue addOperation:operation];
[operation release];
//TO DO: Add any post processing code here, BUT how do we know when it is done???
ConfirmationViewController *otherVC;
//show confirm
//if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
//{
// otherVC = [[ConfirmationViewController alloc] initWithNibName:#"ConfirmationViewPad" bundle:nil];
//}
//else
{
otherVC = [[ConfirmationViewController alloc] initWithNibName:#"ConfirmationView" bundle:nil];
}
//TO DO: Let's put this in a struct
otherVC.strConfirmation = strResponse;
otherVC.strCardType = strCardType;
otherVC.strCardNumber = txtCardNumber.text;
otherVC.strExpires = txtExpires.text;
otherVC.strCustomerEmail = txtEmail.text;
[self.navigationController pushViewController:otherVC animated:YES];
[otherVC release];
otherVC = nil;
}
So far, that works pretty well except that we don't yet have a way to know when the background task is complete. Only when it is complete, can we process the results of the background task. Right now, it doesn't work because there is not synchronization to the two. How to solve?
One other thing, noticed that a spinner is now displayed in the status bar. That is a good thing but it doesn't seem to be going away after the background task has completed? What to do?
Thanks in advance.
Your options are, briefly:
key value observe the 'operationCount' property on NSOperationQueue and wait for it to reach 0 (or, equivalently, the 'operations' property and check the count)
have your operations fire off a little notification that they're done (probably on the main thread with performSelectorOnMainThread:...) and wait until the correct number of notifications have been received.
[EDIT: I see you've asked specifically about the old SDK 3.0. In that case, observe operations and check count because the operationCount property postdates SDK 3.0]
There's no automatic system for starting and stopping a spinner in the general case. You'll have to talk to it yourself. However, a neat thing about a spinner is that it continues spinning even if the main thread is blocked, so if you're thread hopping just for that purpose then you don't actually need to.
A spinner appears in the status bar to show data fetches, I believe. If it continues spinning then you still have URL requests ongoing, whether or not you're actually waiting for the results.

Async call in Objective-C

I'm trying to get data from a website- xml. Everything works fine.
But the UIButton remains pressed until the xml data is returned and thus if theres a problem with the internet service, it can't be corrected and the app is virtually unusable.
here are the calls:
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if(!appDelegate.XMLdataArray.count > 0){
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[appDelegate GetApps]; //function that retrieves data from Website and puts into the array - XMLdataArray.
}
XMLViewController *controller = [[XMLViewController alloc] initWithNibName:#"MedGearsApps" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
}
It works fine, but how can I make the view buttons functional with getting stuck. In other words, I just want the UIButton and other UIButtons to be functional whiles the thing works in the background.
I heard about performSelectorInMainThread but I can't put it to practice correctly.
You don’t understand the threading model much and you’re probably going to shoot yourself in the foot if you start adding asynchronous code without really understanding what’s going on.
The code you wrote runs in the main application thread. But when you think about it, you don’t have to write no main function — you just implement the application delegate and the event callbacks (such as touch handlers) and somehow they run automatically when the time comes. This is not a magic, this is simply a Cocoa object called a Run Loop.
Run Loop is an object that receives all events, processes timers (as in NSTimer) and runs your code. Which means that when you, for example, do something when the user taps a button, the call tree looks a bit like this:
main thread running
main run loop
// fire timers
// receive events — aha, here we have an event, let’s call the handler
view::touchesBegan…
// use tapped some button, let’s fire the callback
someButton::touchUpInside
yourCode
Now yourCode does what you want to do and the Run Loop continues running. But when your code takes too long to finish, such as in your case, the Run Loop has to wait and therefore the events will not get processed until your code finishes. This is what you see in your application.
To solve the situation you have to run the long operation in another thread. This is not very hard, but you’ll have to think of a few potential problems nevertheless. Running in another thread can be as easy as calling performSelectorInBackground:
[appDelegate performSelectorInBackground:#selector(GetApps) withObject:nil];
And now you have to think of a way to tell the application the data has been loaded, such as using a notification or calling a selector on the main thread. By the way: storing the data in the application delegate (or even using the application delegate for loading the data) is not very elegant solution, but that’s another story.
If you do choose the performSelectorInBackground solution, take a look at a related question about memory management in secondary threads. You’ll need your own autorelease pool so that you won’t leak autoreleased objects.
Updating the answer after some time – nowadays it’s usually best to run the code in background using Grand Central Dispatch:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// No explicit autorelease pool needed here.
// The code runs in background, not strangling
// the main run loop.
[self doSomeLongOperation];
dispatch_sync(dispatch_get_main_queue(), ^{
// This will be called on the main thread, so that
// you can update the UI, for example.
[self longOperationDone];
});
});
Use NSURLConnection's connectionWithRequest:delegate: method. This will cause the specified request to be sent asynchronously. The delegate should respond to connection:didReceiveResponse: and will be sent that message once the response is completely received.
You can make use of a background operation that gets pushed into the operation queue:
BGOperation *op = [[BGOperation alloc] init];
[[self operationQueue] addOperation:op];
[op release];
I've created specific "commands" that get executed in the background:
#implementation BGOperation
# pragma mark Memory Management
- (BGOperation *)init
{
if ((self = [super init]) != nil)
/* nothing */;
return self;
}
- (void)dealloc
{
self.jobId = nil;
[super dealloc];
}
# pragma mark -
# pragma mark Background Operation
- (void)main
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[appDelegate GetApps];
[pool release];
return;
}
#end
After completion it might be a good idea to send a notification to the main thread because the internal database has been changed.
It looks as if you might be using NSURLConnection inside your getApps method. If so, you should convert it to an asynchronous call.