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!
Related
I'm making a synchronize function that syncs local Core Data with the server. I want to make the synchronizations happen in the background without disrupting user interaction. When I receive the response (whether success or failure) the app should display a message somewhere on the screen to notify the user about the outcome.
UIAlertController is not a good choice because it will block user action.
Currently I'm using SVProgressHUD:
__weak StampCollectiblesMainViewController *weakSelf = self;
if ([[AppDelegate sharedAppDelegate] hasInternetConnectionWarnIfNoConnection:YES]) {
[_activityIndicator startAnimating];
[Stamp API_getStampsOnCompletion:^(BOOL success, NSError *error) {
if (error) {
[_activityIndicator stopAnimating];
[SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeClear];
[SVProgressHUD setAnimationDuration:0.5];
[SVProgressHUD showErrorWithStatus:#"error syncronize with server"];
}
else {
[_activityIndicator stopAnimating];
[featuredImageView setImageWithURL:[NSURL URLWithString:[Stamp featuredStamp].coverImage] usingActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[yearDropDownList setValues:[Stamp yearsDropDownValues]];
[yearDropDownList selectRow:0 animated:NO];
[weakSelf yearDropDownListSelected];
[SVProgressHUD dismiss];
}
}];
}
Is there a modification I can make so the user can still interact with the app? I just want to show the message without taking up too much space. Any help is much appreciated. Thanks.
Looks like the easiest thing will be to use SVProgressHUDMaskTypeNone.
Also check out this issue.
Sorry but you gonna have to build your own custom view.
In fact it's not that difficult. What I would do is simply add a small view on the top of the screen with your custom message and a close button (to allow user to hide quickly the message). This is usually done by adding this new view to the current window, so that it will be on the top of every view and won't block the UI (except the part hidden by that view :) )
I have started learning "GCD".
I found that when we use the FirstWay, the alertView will be called after all the NSLog functions are done with print.
But when we use the SecondWay, the alertView will be called before the NSLog function.
Why do these two methods run different results?
Can these two methods not be asynchronous methods?
My English is so poor, I hope somebody can understand my description.
Thank you!
/*
*FirstWay
*/
// dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// dispatch_async(globalQueue, ^{
// for (int i = 0; i < 10000 ; i++) {
// NSLog(#"i = %d", i);
// }
//
// dispatch_async(dispatch_get_main_queue(), ^{
// NSLog(#"i = %d", i);
// UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Title " message:#"Message" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Yes", nil];
// [alertView show];
// });
// });
/*
*SecondWay
*/
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Title " message:#"Message" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Yes", nil];
[alertView show];
dispatch_async(globalQueue, ^{
for (int i = 0; i < 10000 ; i++) {
NSLog(#"i = %d", i);
}
});
});
Here you are submitting a job (task) to a queue by calling the dispatch_async function. Feature of dispatch_async is that it returns immediately, and the submitted block will execute asynchronously in the background.Because UI related tasks run only in the main queue, so you need to return to the main queue if you are in the background queue. And you simply call the dispatch_get_main_queue() method to update the UI.
Here in your case:
In the first case you are submitting the task to the global_queue whereas in the second case you have submitted your task to the main queue.
In the first one , for-loop code is executed from the global_queue and task inside inner dispatch handler is dispatched to the main_queue where it updates the UI (i.e shows the alert view).
In the second one , you are simply submitting to main_queue and you are updating the UI (showing alert) and then you are dispatching to global queue to do for-loop task.
I hope now it helps if not ,feel free to comment.
More Info:
Don't get confused, first just look at the outer dispatch. You are dispatching something to work in the background.You are willing to do that task in the other thread so that your main thread won't have to wait for some long task.(if your main thread takes much time waiting for something then the OS kills your application so GCD is here to take us out of that easily).
Secondly just think the code inside of same outer dispatch as a instructions (line by line codes only). Don't mingle with the inner dispatch handler. Third Go through those line by line.Code upto the start of inner dispatch is the block of code you wish to do in the background.And the reason you nest the inner dispatch is to come out of that background thread(in most cases) in your first case you have done it to show the alert view after your for-loop code completes.
I have been reading up on Objectice-C blocks as I have been running into them more and more lately. I have been able to solve most of my asynchronous block execution problems, however I have found one that I cannot seem to fix. I thought about making an __block BOOL for what to return, but I know that the return statement at the end of the method will be executed before the block is finished running. I also know that I cannot return a value inside the block.
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([identifier isEqualToString:#"Reminder Segue"]) {
eventStore = [[EKEventStore alloc] init];
[eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
if (!granted) {
UIAlertView *remindersNotEnabledAlert;
remindersNotEnabledAlert = [[UIAlertView alloc] initWithTitle:#"Reminders Not Enabled" message:#"In order for the watering reminder feature to function, please allow reminders for the app under the Privacy menu in the Settings app." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
//I would like to put a simple return NO statement here, but I know it is impossible
}
}];
}
return YES;
}
How do I create a simple return statement from a block?
While the immediate idea might be to make your asynchronous request synchronous, that's rarely a good idea, and do to so in the middle of a segue, such as this case, is likely to be problematic. It's almost never a good idea to try to make an asynchronous method synchronous.
And, as smyrgl points out, the idea of "can't I just return a value from the block" is intuitively attractive, but while you can define your own blocks that return values (as Duncan points out), you cannot change the behavior of requestAccessToEntityType such that it returns a value in that manner. It's inherent in its asynchronous pattern that you have to act upon the grant state within the block, not after the block.
So, instead, I would suggest a refactoring of this code. I would suggest that you remove the segue (which is likely being initiated from a control in the "from" scene) and not try to rely upon shouldPerformSegueWithIdentifier to determine whether the segue can be performed as a result of a call to this asynchronous method.
Instead, I would completely remove that existing segue and replace it with an IBAction method that programmatically initiates a segue based upon the result of requestAccessToEntityType. Thus:
Remove the segue from the button (or whatever) to the next scene and remove this shouldPerformSegueWithIdentifier method;
Create a new segue between the view controllers themselves (not from any control in the "from" scene, but rather between the view controllers themselves) and give this segue a storyboard ID (for example, see the screen snapshots here or here);
Connect the control to an IBAction method, in which you perform this requestAccessToEntityType, and if granted, you will then perform this segue, otherwise present the appropriate warning.
Thus, it might look something like:
- (IBAction)didTouchUpInsideButton:(id)sender
{
eventStore = [[EKEventStore alloc] init];
[eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
// by the way, this completion block is not run on the main queue, so
// given that you want to do UI interaction, make sure to dispatch it
// to the main queue
dispatch_async(dispatch_get_main_queue(), ^{
if (granted) {
[self performSegueWithIdentifier:kSegueToNextScreenIdentifier sender:self];
} else {
UIAlertView *remindersNotEnabledAlert;
remindersNotEnabledAlert = [[UIAlertView alloc] initWithTitle:#"Reminders Not Enabled" message:#"In order for the watering reminder feature to function, please allow reminders for the app under the Privacy menu in the Settings app." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[remindersNotEnabledAlert show];
}
});
}];
}
You CAN return a value from a block, just like from any function or method. However, returning a value from a completion block on an async method does not make sense. That's because the block doesn't get called until after the method finishes running at some later date, and by then, there is no place to return a result. The completion method gets called asynchronously.
In order to make a block return a value you need to define the block as a type that does return a value, just like you have to define a method that returns a value.
Blocks are a bit odd in that the return value is assumed to be void if it's not specified.
An example of a block that returns a value is the block used in the NSArray method indexOfObjectPassingTest. The signature of that block looks like this:
(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
The block returns a BOOL. It takes an object, an integer, and a pointer to a BOOL as parameters. When you write a block of code using this method, your code gets called repeatedly for each object in the array, and when you find the object that matches whatever test you are doing, you return TRUE.
If you really want to make a block synchronous (although I question the validity of doing so) your best bet is to use a dispatch_semaphore. You can do it like this:
dispatch_semaphore_t mySemaphore = dispatch_semaphore_create(0);
__block BOOL success;
[eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
success = granted;
dispatch_semaphore_signal(mySemaphore);
}];
dispatch_semaphore_wait(mySemaphore, DISPATCH_TIME_FOREVER);
However again I don't think you want to do this, especially in a segue as it will stall the UI. Your better bet is to rearchitect what you are doing so that you don't have a dependency on the async process being completed in order to continue.
From a view controller, as a result of a button action, I need to create a custom object that manages a set of asynchronous remote service calls, and call the method of such object that fires those service calls. I need the view controller to wait for all the async networking operations to have finished in order to update its view. Since the networking operations are async, I don't know how I'd communicate from the custom object managing this tasks to the view controller when all operations are done.
Here is the code I currently have. The code snippet in the view controller is like this (result var is not currently used):
- (void)loadData
{
BOOL __block result = NO;
dispatch_queue_t queue = dispatch_queue_create(dataLoadQueue, NULL);
dispatch_async(queue,^{
Loader *loader = [[Loader alloc] init];
[loader loadData];
dispatch_async(dispatch_get_main_queue(), ^{
if (result) {
// Update view and notify success
}
else {
// Update view and notify error
}
});
});
dispatch_release(queue);
}
And this is the loader custom object side:
- (void)loadData
{
if ([Reachability checkNetStatus]) {
Service1 *service1 = [[Service1 alloc] init];
[service1 callAsyncService];
Service2 *service2 = [[Service2 alloc] init];
[service2 callAsyncService];
// More service calls
}
else {
// Notify network not reachable
}
}
Objects service1, service2... serviceN conform the NSURLConnectionDelegate and I notify they have finished in its connectionDidFinishLoading: by means of the NSNotificationCenter (loader object is listening for such notifications). Then, I donĀ“t know what is the correct way of making loader wait for all the networking operations, and notify back the view controller.
Thanks in advance
There are probably lots of ways you could do this. First, I don't think there's any need to use GCD in the view controller -- loader is already doing things asynchronously, so the creation of loader is fast.
As for how Loader knows when all its network operations are done, you could just keep a list of strings in a mutable array, like "1 done", "2 done", etc. that would be the same as strings sent in the user info of the notifications called in connectionDidFinishLoading:. All the services could send the same notification, but with different user info. In the selector for the observer, remove the string identical to the one in the user info, and check if the array is empty -- when it is, all your services are done. At that point, I would use a delegate method to pass back the data to the view controller. Something like this in Loader:
- (void)viewDidLoad {
[super viewDidLoad];
self.doneStrings = [#[#"1 done", #"2 done", #"3 done", #"4 done"] mutableCopy];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notificationReceived:) name:#"SeriveFinishedNotification" object:nil];
}
-(void)notificationReceived:(NSNotification *) aNote {
[self.doneStrings removeObjectIdenticalTo:[aNote.userInfo objectForKey:#"doneString"]];
if (self.doneStrings.count == 0)
[delegate doSomethingWithTheData: theData];
}
You would probably need to some other things like handle the case where some of the network operations fail.
If you want to wait until the async tasks were done, you can use a semaphore. See the example below, the logic is pretty simply. I think you can easily adapt to your case.
//create the semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[objectManager.HTTPClient deletePath:[address addressURL] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
//some code here executed in background
dispatch_semaphore_signal(semaphore); //sends a notification to release the semaphore
}failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//some other code here also executed in background
dispatch_semaphore_signal(semaphore); //sends a notification to release the semaphore
}];
//holds the thread until the dispatch_semaphore_signal(semaphore) is send
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW))
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];
}
You haven't shared the details of how these asynchronous requests work, but another approach is to make these asynchronous requests NSOperation objects that you submit to a NSOperationQueue. (AFNetworking is an example of this sort of implementation.) When you do that, you can create yet another NSOperation to be triggered upon the completion of the network request operations, by make it dependent upon those network request operations. Thus it will only run when all of the network requests are done. Using an NSOperation-based solution enjoys other benefits, too (e.g. you can use setMaxConcurrentOperationCount to let you enjoy concurrency, but not run too many concurrent requests at any given time).
References
Ray Wenderlich's How To Use NSOperations and NSOperationQueues
Defining a Custom Operation Object in Apple's Concurrency Programming Guide
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.