Just learning how to allocate tasks among threads, or dispatch asynchronously. I understand that any operation that "touches" a view must be done on the main thread. What about: UIImageWriteToSavedPhotosAlbum? I would assume this could be done on a background thread, but am I mistaken?
Also, if it should be done on a background thread, is there a difference between these two calls below, as one saves a UIImage and the other saves a UIImage from a view?
UIImageWriteToSavedPhotosAlbum(_someUIImage ,nil,nil,nil);
UIImageWriteToSavedPhotosAlbum(_imageView.image ,nil,nil,nil);
By the way I am using this setup to run an HUD in the main thread and to tasks in the background, that is my intention.
[HUD_code showMessage:#"saving image"];
dispatch_queue_t concurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
UIImageWriteToSavedPhotosAlbum(someUIImage ,nil,nil,nil);
dispatch_async(dispatch_get_main_queue(), ^{
[HUD_code dismiss];
});
});
UIKit classes are documented to be usable from the main thread only, except where documented otherwise. (For example, UIFont is documented to be thread-safe.)
There's no explicit blanket statement about the thread safety of UIKit functions (as distinct from classes), so it's not safe to assume they generally thread-safe. The fact that some UIKit functions, like UIGraphicsBeginImageContext, are explicitly documented to be thread-safe, implies that UIKit functions are not generally thread-safe.
Since UIImageWriteToSavedPhotosAlbum can send an asynchronous completion message, you should just call it on the main thread and use its completion support to perform your [HUD_code dismiss].
Here is my latest code after reading the answers, if anyone cares to know, or to comment (appreciated).
-(void)saveToLibrary {
if (_imageView.image != NULL) {
messageHUD = #"Saving Image...";
[SVProgressHUD showWithStatus:messageHUD];
UIImageWriteToSavedPhotosAlbum(_imageView.image, self, #selector(image:didFinishSavingWithError:contextInfo:), nil);
}
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
UIAlertView *alert;
// Unable to save the image
if (error) {
alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Unable to save image to Photo Album."
delegate:self cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
}else {// All is well
messageHUD = #"Success!\nImage Saved.";
[SVProgressHUD showSuccessWithStatus:messageHUD];
[self myPerformBlock:^{[SVProgressHUD dismiss];} afterDelay:0.5];
}
}
The myPerformBlock is from the following link https://gist.github.com/955123
I would assume this could be done on a background thread, but am I mistaken?
Honestly, I would assume it too, since this has absolutely nothing to do with updating the UI, it's just some file operation. However, Apple's documentation says that every call to UIKit needs to be performed on the main thread (Except where something else is explicitly stated). This function is no exception, you have to call it on the main thread.
By the way, this function is asynchronous itself. It will notify the callback object/selector supplied as its 2nd and 3rd arguments when the image is saved, and thus it doesn't block the UI.
Related
I implemented login method in this way:
[KVNProgress show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//some error handling like:
if ([_usernameField.text length] < 4) {
[KVNProgress showErrorWithStatus:#"Username too short!"];
_passwordField.text = #"";
return;
}
//Then I call login web service synchronously here:
result = [ServerRequests login];
dispatch_async(dispatch_get_main_queue(), ^{
if(!result)
{
[KVNProgress showErrorWithStatus:#"problem!" completion:NULL];
_passwordField.text = #"";
}
else if([result.successful boolValue])
{
[KVNProgress showSuccessWithStatus:result.message];
}
});
});
It crashed mostly and by surrounding blocks with only Main Queue (no priority default one) that solved! but the problem is:KVNProgress is only showing in error handling area not the next part that we call web service. It's not user friendly at all! Any idea is welcomed :)
You MUST call methods that update the user interface in any way from the main thread, as per the UIKit documentation:
For the most part, use UIKit classes only from your app’s main thread. This is particularly true for classes derived from UIResponder or that involve manipulating your app’s user interface in any way.
I suggest you try to limit the number of callbacks you make to the main thread, so therefore you want to batch as much user interface updates together as you can.
Then all you have to do, as you correctly say, is to use a dispatch_async to callback to your main thread whenever you need to update the UI, from within your background processing.
Because it's asynchronous, it won't interrupt your background processing, and should have a minimal interruption on the main thread itself as updating values on most UIKit components is fairly cheap, they'll just update their value and trigger their setNeedsDisplay so that they'll get re-drawn at the next run loop.
From your code, it looks like your issue is that you're calling this from the background thread:
if ([_usernameField.text length] < 4) {
[KVNProgress showErrorWithStatus:#"Username too short!"];
_passwordField.text = #"";
return;
}
This is 100% UI updating code, and should therefore take place on the main thread.
Although, I have no idea about the thread safety of KVNProgress, I assume it should also be called on the main thread as it's presenting an error to the user.
Your code therefore should look something like this (assuming it's taking place on the main thread to begin with):
[KVNProgress show];
//some error handling like:
if ([_usernameField.text length] < 4) {
[KVNProgress showErrorWithStatus:#"Username too short!"];
_passwordField.text = #"";
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Then I call login web service synchronously here:
result = [ServerRequests login];
dispatch_async(dispatch_get_main_queue(), ^{
if(!result) {
[KVNProgress showErrorWithStatus:#"problem!" completion:NULL];
_passwordField.text = #"";
} else if([result.successful boolValue]) {
[KVNProgress showSuccessWithStatus:result.message];
}
});
});
I'm working with some code that downloads data. The code is using blocks as callbacks. There are several download methods with very similar code: In the callback block they show a UIAlertView if something goes wrong. The alert view always looks like this:
[req performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if(error) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kFailed object:nil];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Connection failed"
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
});
}
}];
I want to move the alert view code to a method of its own since it's called several times with the same parameters. Should I move the dispatch_async() to the method too, or should I just wrap calls to that method in dispatch_async()?
This has nothing to do with wrong or correct.
Advantage: If you place the dispatch_async() inside the method, you can send the message from every place of your program regardless of the thread you are running in.
Disadvantage: If you place the dispatch_async() inside the method, the code is always executed async even the message is sent from the main thread. (In this case dispatch_async() is simply not necessary and a dispatch_sync() would dead lock.)
And vice versa.
To me something different is more important: Define a layer of "dispatch methods". Only use dispatch_async() and dispatch_sync() inside this layer, not in layers built on top of this, not in layers built underneath this.
From higher levels of your software use always this layer. Inside the layer use only methods on a lower layer.
You can do it either way. Functionally these two blocks of code are the same:
Method 1
//.... Assuming this is called in a block
dispatch_async(dispatch_get_main_queue(), ^{
[self showMyAlertView];
});
- (void) showMyAlertView {
// Show the alert view and other stuff
}
Method 2
//.... Assuming this is also called in your block
[self showMyAlertView];
- (void) showMyAlertView {
dispatch_async(dispatch_get_main_queue(), ^{
// Show the alert view and other stuff
});
}
Obviously the second way requires the fewest lines of code, but if you want to do other stuff asynchronously (besides show your alert view), you might want to do method 1 so you can add other stuff to the queue.
Hope this helped!
I'm looking for a common and elegant way to manage interfaces update.
I know that user interface code must be run in main thread, so when i need some computation o network task i use GDC with this pattern:
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue, ^() {
//Backgroud code
dispatch_sync(dispatch_get_main_queue(), ^{
//Update the UI
}
}
The problem with this code is that i need always check if user has changed view during my computation, so the code is like:
dispatch_sync(dispatch_get_main_queue(), ^{
if (mylabel != nil) && ([mylabel superview] != nil) {
mylabel.text = _result_from_computation_;
}
}
There is some best ways?
Thanks.
You pretty well have it. However, in case you want to do more reading or want a more thorough explanation of what's going on...
You should read the Apple Docs Grand Central Dispatch (GCD) Reference and watch the WWDC 2012 video, Session 712 - Asynchronous Design Patters with Blocks, GCD and XPC.
If you're working with iOS, you can disregard XPC (interprocess communication) as it's not supported by the current OS version (6.1 at the time of this writing).
Example: Load a large image in the background and set the image when completed.
#interface MyClass ()
#property (strong) dispatch_block_t task;
#end
#implementation MyClass
- (void)viewDidLoad {
self.task = ^{
// Background Thread, i.e., your task
NSImage *image = [[NSImage alloc] initWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
// Main Thread, setting the loaded image
[view setImage:image];
});
});
}
- (IBAction)cancelTaskButtonClick:(id)sender { // This can be -viewWillDisappear
self.task = nil; // Cancels this enqueued item in default global queue
}
- (IBAction)runTaskButtonClick:(id)sender {
// Main Thread
dispatch_queue_t queue;
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, self.task);
}
In order to cancel and reload the interface later, all you have to do is set the dispatch_block_t variable to nil.
Perhaps more specifically to your problem, this example piece of code deals with Reading Data from a Descriptor, i.e., either the disk or network.
Typically, you would use the Call-Callback pattern which essentially gets a background thread, executes a task, and when completed calls another block to get the main thread to update the UI.
Hope this helps!
You can check the view window property:
if (myLabel.window) {
// update label
}
this is redundant if (label != nil) since if label is nil, then all label properties will also be nil (or zero) and setting them will not raise an exception.
This is what I am doing.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData* data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://myurl"]]];
dispatch_sync(dispatch_get_main_queue(), ^{
if(!data) {
// data not recieved or bad data. Initiate reachability test
// I have built a wrapper for Reachability class called ReachabilityController
// ReachabilityController also notifies the user for avaibility, UI
ReachabilityController *reachability = [[ReachabilityController alloc] init];
[reachability checkReachability];
return;
}
//update table
});
});
My problem is the reachability test is being done in the main queue, which often freezes the UI. I want to run in a background mode.
I want to process the ReachabilityTest in a background mode or in a low priority mode. But again, my reachability controller does notify user of the current net avaibility, so at some point i will have to use main queue again.
I strongly believe that there must be a better way.
This is, however, a correct way. It doesn't look entirely pretty, but that doesn't mean it's incorrect. If you want your code to look 'cleaner' you might wanna take a look at NSThread and work your way through it, but this is a far easier approach.
To make it look easier in my project we made a simple class called dispatcher that uses blocks:
+ (void)dispatchOnBackgroundAsync:(void(^)(void))block {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block);
}
+ (void)dispatchOnMainAsync:(void(^)(void))block {
dispatch_async(dispatch_get_main_queue(), block);
}
used like this:
[Dispatcher dispatchOnBackgroundAsync:^{
// background thread code
[Dispatcher dispatchOnMainAsync:^{
// main thread code
}];
}];
I have an app that I'm accessing a remote website with NSURLConnection to run some code and then save out some XML files. I am then accessing those XML Files and parsing through them for information. The process works fine except that my User Interface isn't getting updated properly. I want to keep the user updated through my UILabel. I'm trying to update the text by using setBottomBarToUpdating:. It works the first time when I set it to "Processing Please Wait..."; however, in the connectionDidFinishLoading: it doesn't update. I'm thinking my NSURLConnection is running on a separate thread and my attempt with the dispatch_get_main_queue to update on the main thread isn't working. How can I alter my code to resolve this? Thanks! [If I need to include more information/code just let me know!]
myFile.m
NSLog(#"Refreshing...");
dispatch_sync( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getResponse:#"http://mylocation/path/to/file.aspx"];
});
[self setBottomBarToUpdating:#"Processing Please Wait..."];
queue = dispatch_queue_create("updateQueue", DISPATCH_QUEUE_CONCURRENT);
connectionDidFinishLoading:
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Contacts..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Emails..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
In my connectionDidFinishLoading: I would try something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^ {
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Contacts..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Emails..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
});
Then all that file access is happening in a background queue so the main queue is not locked up. The main queue will also complete this call to connectionDidFinishLoading much more quickly, since you're throwing all the hard work onto the default queue instead, which should leave it (and the main thread) ready to accept your enqueuing of the updates to the UI which will be done by the default queue as it processes the block you just enqueued to it.
The queue handover becomes
main thread callback to connectionDidFinishLoad:
rapid handoff to default global queue releasing main thread
eventual hand off to main queue for setBottomBarToUpdating: calls
performing main queue blocks on main thread to properly update UI
eventual completion of blocks on main queue
eventual completion of blocks on default queue
You've increased concurrency (good where you've good multi-core devices) and you've taken the burden of I/O off the main thread (never a good place for it) and instead got it focused on user interface work (the right place for it).
Ideally you woud run the NSURLConnection run loop off the main thread too, but this will might be enough for you to get going.
Which run loop are you running the NSURLConnection in? If it's the main loop, you're queueing up the setBottomBarToUpdating: calls behind the work you're already doing, hence the probable reason why you're not seeing the UI update.
You could also give performSelectorOnMainThread try like so:
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
[self performSelectorOnMainThread:#selector(setBottomBarToUpdating) withObject:#"Updating Contacts..." waitUntilDone:false];
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
[self performSelectorOnMainThread:#selector(setBottomBarToUpdating) withObject:#"Updating Emails..." waitUntilDone:false];
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}