Ignore calls to dispatch_async when a request is being handled - ios

I have an iPhone app where based on some parameters an image gets recreated. Since this image recreation can take some time, I use a separate thread to create the image.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0);
// costly drawing
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.image = newImage;
});
});
The parameters affecting the recreation of this image can vary faster than the image can get recreated, so I'd like to "pause" recreation when needed and only perform one such dispatch_async call at a time.
Then as more requests to recreate the image arrive, only remember the last one (with the most up to date parameters), and as soon as the image recreation finished start one for those parameters.
It doesn't matter that all the other calls are never done, the image would be overwritten anyway.
What's the best way to achieve this?

You may want to consider using NSOperationQueue since you can cancel existing queue items every time a new one is added.
Using dispatch_async will run whatever you place in the block until completion (unless you suspend the entire queue) so there's not a great way of stopping prior queue items without setting some sort of a cancellation flag (in which case, they are just short-circuited but the block is still run to completion).
NSOperationQueue is built on top of GCD so it provides the same backgrounding capabilities, it just gives you more control over the queue, which is what you need in this case. It can also be run concurrently on multiple threads, but you shouldn't need that.

Related

Multiple "Main Queue" Tasks For User Perspective

This is perhaps more existential than a concrete question, but I'm struggling with a bit of a user experience issue in my app.
In my app, I have a task that converts a UIView to an UIImage, does some off-screen processing, and updates the UI for the user. All of this happens on the Main Queue, as is required for such UIKit tasks.
I want to have an activity indicator in my app (I'm using a custom designed one, but a regular UIActivityIndicator demonstrates the same issue), which I also have running on the Main Queue, prior to the aforementioned task.
My issue is that once the UIView processing kicks in, my activity indicator freezes until the task completes. This is obviously due to the main queue handling another, more intensive task, hereby hanging the spinner. My curiosity is; how can I ensure the spinner continues, regardless of the UI work happening on the main queue?
Thanks!
I'm afraid this is impossible unless you do the heavyweight operation on the background thread.
You can try to give the main thread a little bit air to breathe by chunking the operation to smaller parts, if that can be done. That would at least allow some updates of the spinner.
While I think that you should keep taking an image and updating the UI on the main thread, considering putting processing the image at the background thread, if that is possible.
I agree with Milan. I'd suggest a slightly different flow:
Start your activity indicator spinning.
Grab your view and convert it to an image.
Pass the image processing to a GCD background queue for processing, and pass in a completion handler.
When the background processing is complete, invoke the completion handler on the main thread. In the body of the completion handler, stop/hide the activity indicator.
I would suggest something like this.
-(void)render {
startSpinner();
backgroundRenderQueue = dispatch_queue_create("backgroundQueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(backgroundQueue, ^{
//Render image here
UIImage *image = [UIImage imageWithCGImage:CGBitmapContextCreateImage(context)];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageview.image = image;
stopeSpinner()
});
});
}

How to check dispatch_async get called or not earlier for same operation

I faced a problem. My application downloading images from server and showing in a UITableView For downloading the images I am using dispatch_async.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// downloading image from server
dispatch_async(dispatch_get_main_queue(), ^{
// update UI for showing downloaded image
});
});
Now the problem is if user scroll up and down very fast then multiple dispatch_async get fired for the same image. I have image id. Is there any way to check whether a dispatch_async get fired or not for that image or not by image id?
You'd have to set up your own mechanism for keeping track of whether there were any pending requests for that image (e.g. a dictionary keyed by the image URL absolute string or something like that). The thing is, it's not as simple as that, as the cell that now needs to be updated may be different than (or in addition to) the cell for which it was originally requested. This gets ugly quickly.
But I'd step back and ask whether this is the root problem, or a symptom of a broader problem. For example, if the user scrolls down relatively quickly to the 100th row. Is that image request backlogged behind the previous 99 rows? When you're in that sort of situation, the problem you describe starts to become serious, because if you now scroll quickly back up to the top before the other 99 images have finished downloading, you'll be issuing requests to get them again.
But, if you change your asynchronous image retrieval to cancel requests for cells that are not visible, suddenly this serious problem becomes somewhat academic. Plus, you have the added benefit that the image for the 100th row that the user rapidly scrolled to is not backlogged behind all of the other image requests, so it appears quite quickly.
This means that you want to employ cancelable asynchronous requests. This would therefore suggest using NSURLSession (or delegate-based NSURLConnection if you have to support OS versions that predate NSURLSession). Also, this would generally make one lean towards operation queues and subclassed asynchronous NSOperation subclass rather than GCD queue suggested by your question (to make them cancelable, asynchronous, and also to constrain the degree of concurrency). And then you have to then implement the code that makes use of all of this cancellation logic that you've so industriously created (e.g. UIImageView category or some image request manager that your UITableViewDataSource and/or UITableViewCell subclass uses).
Also, you want to make sure that you employ a cache mechanism (possibly both to memory and persistent storage). This way, if you've already retrieved a particular image, when you scroll back to that row, the image is ready for you to retrieve it, not requiring another network call.
I know that you have said that you want to do this yourself, but this is a non-trivial amount of work to do properly. This is why I (and others) suggest you consider checking out one of the UIImageView categories that allow you to asynchronously retrieve an image. See the UIImageView categories provided by SDWebImage or AFNetworking. If you're properly dequeuing cells, these achieve very responsive UI while retrieving the images asynchronously. They effectively cancel requests for cells that are reused, thereby prioritizing visible cells. They also cache results resulting in good performance as you scroll back.
For that you should store in some place the downloaded images or mark if there is a current operation for them. So you will be doing basically a image cache mechanism. I will advise that instead to do that you can use a well tested library for that like SDWebImage that precisely deal with download images and cached them to memory or disk.
Keep it simple: use NSURLSession rather than rolling your own attempt at asynchronous URL fetches. Rely on its caching to avoid repeat downloads where the previous download completed. Then all you have to worry about is repeat requests for downloads that are currently ongoing.
The easiest thing is probably just to keep two dictionaries: URL to NSURLSessionDataTask and URL to array of completion handlers.
When a cell wants a URL it calls into the handler for those two dictionaries, supplying a completion handler.
If there's no data task for that URL, the handler creates one and starts it, also creating a dictionary containing just that completion handler. Both are put into their respective dictionaries.
If there is a data task for that URL, the completion handler is added to the array in the second dictionary.
Tasks are created with a completion block that calls the handler, which upon receipt will remove the task from the first dictionary and call everything in the array in the second dictionary, then removing the array from there.
Given that you're view related, the handler permits external calls from the main queue only. It will ensure that the work subsequent to a completed data task is also performed on the main queue. That means you'll have no concurrency issues.

Updating two UILabels concurrently

I have two UILabels that require updating on millisecond basis. I ran my app and noticed that one of the UILabels ("The Second UILabel") updates slower than the first one. It's not a large difference but it's perceptible.
The Second UILabel does a short computation before displaying its results on the UILabel. I suspect I may need to push this to a background thread.
I have used threads in Java but would like to explore GCD, operations queue, dispatch queue, etc. I've read a lot on them (especially from Apple website) but just can't make sense of them.
So I need to check with the gurus here:
Q1: Is The Second UILabel perception problem due to updating of the UI Thread that lead to thread contention?
Q2: Does my app updating The Second UILabel require a background thread or will GCD and the like suffice?
The answer.
dispatch_queue_t background_queue = dispatch_queue_create("label", NULL);
dispatch_async(background_queue, ^{
// do some stuff that takes a long time here...
// follow up with some stuff on the main queue
dispatch_async(dispatch_get_main_queue(), ^{
// Typically updating the UI on the main thread.
});
});

dispatch_sync inside a dispatch_async

I just wanted to confirm why this is needed.
I added this code to the KIImagePager (a cocoapod) to load images that are local to the app (the default code loads images from a url).
Here's my working code based off what a coworker suggested:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
[imageView setImage:[UIImage imageNamed:[aImageUrls objectAtIndex:i]]];;
});
});
I noticed that if I take out the inner dispatch_sync, it works but not in the way I want (some of the images on the image pager scrollview aren't loaded yet when I start scrolling). But they do eventually load.
My question is this, does the sync call on the main queue get the image back to the UI (which is on the main queue)? Because it does work with the second async removed.
The internal dispatch executes its code block on the main thread. This is required because all UI operations must be performed on the main thread. And you're image downloading code (context in which this snippet is executed) may be on a background thread.
The external dispatch executes its block on a background thread. The block its given is the one that executes on the main thread. Thus, the external block can be safely removed.
Hrs an outline of the idiom you're using.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// do blocking work here outside the main thread.
// ...
// call back with result to update UI on main thread
//
// what is dispatch_sync? Sync will cause the calling thread to wait
// until the bloc is executed. It is not usually needed unless the background
// background thread wants to wait for a side effect from the main thread block
dispatch_sync(dispatch_get_main_queue(), ^{
// always update UI on main thread
});
});
You should only work with UI objects on the main thread. If you don't, you will run into a couple of problems. The first, as you saw, is that UI objects will be delayed in updating. The second is that the app could crash if you try to change UI objects simultaneously from multiple threads. You should only work with UI objects on the main thread.

How do I prepare my UI in a background thread?

A UIViewController takes about half a second to load its contents and appear on screen. How can I make them all load in the background and appear when they're ready?
There is a LazyTableImages sample on the Apple developer site.
It shows how to perform the heavy lifting in a background thread and update the UI on the main thread.
PerformSelectorInBackground:withObject: is a possible solution, although a more modern method would be to use asynchronous blocks. You can run code on the main thread from within these blocks to update the UI Safely.
The Concurrency Programming Guide is a good place to find more information and examples of this.
A Background Thread cant update the UI,you can perform all the processing logic in background thread and call the main thread for UI update
Example to load a tableView with Data ,use the background thread to process everything and load the Data, call [tableView reloadData] using the main thread, see Grand central Dispatching to know how to Work with Threads in IOS..
Hope it Helps
Create a GCD queue to process your work in a background thread (read the docs, because my "create" label and options may not be what you want).
You send it to the queue asynchronously, meaning that the call to dispatch_async will make appropriate arrangements for the block of code you give it to run in another thread and it will then return back to you "immediately."
All the work in the block you give it will be executed on a separate thread. Note, at the end, you do another call, this time with the well know main queue. This arranges for that code to run in the main thread, which is mandatory for any UI work.
Also, you really should read the documentation on GCD and especially blocks, because there are memory and cycle considerations. Good luck.
dispatch_queue_t workQ = dispatch_queue_create("bgWorkQ", 0);
dispatch_async(workQ, ^{
// This code is now running in a background thread.
// Do all your loading here...
// When ready to touch the UI, you must do that in the main thread...
disptach_async(dispatch_get_main_queue(), ^{
// Now, this code is running in the main thread.
// Update your UI...
});
});
dispatch_release(workQ);
The easiest way is to use NSObject's - (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg You just pass it a selector and it will happy in the background and not block your UI. Be aware however that there are rules to background threads.
You shouldn't update your UI from the background thread. And you need to make sure the methods you're calling are thread safe.

Resources