GCD - main vs background thread for updating a UIImageView - ios

I'm new to GCD and blocks and am easing my way into it.
Background: I'm working on a lazy loading routine for a UIScrollView using the ALAssetsLibrary. When my UIScrollView loads I populate it with the aspectRatioThumbnails of my ALAssets and then as the user scrolls, I call the routine below to load the fullScreenImage of the ALAsset that is currently being displayed. It seems to work.
(if anyone has a better lazy loading routine please post a comment. I've looked at all I could find plus the WWDC video but they seem to deal more with tiling or have much more complexity than I need)
My question: I use a background thread to handle loading the fullScreenImage and when that is done I use the main thread to apply it to the UIImageView. Do I need to use the main thread? I've seen that all UIKit updates need to happen on the main thread but I am not sure if that applies to a UIImageView. I was thinking it does, since it is a screen element but then I realized that I simply didn't know.
- (void)loadFullSizeImageByIndex:(int)index
{
int arrayIndex = index;
int tagNumber = index+1;
ALAsset *asset = [self.assetsArray objectAtIndex:arrayIndex];
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage *tmpImage = [[UIImage alloc] initWithCGImage:asset.defaultRepresentation.fullScreenImage];
if ([weakSelf.scrollView viewWithTag:tagNumber] != nil){
dispatch_async(dispatch_get_main_queue(), ^{
if ([weakSelf.scrollView viewWithTag:tagNumber]!= nil){
UIImageView * tmpImageView = (UIImageView*)[weakSelf.scrollView viewWithTag:tagNumber];
tmpImageView.image = tmpImage;
}
});
}
});
}

Yes, you need to use the main thread whenever you're touching UIImageView, or any other UIKit class (unless otherwise noted, such as when constructing UIImages on background threads).
One commentary about your current code: you need to assign weakSelf into a strong local variable before using it. Otherwise your conditional could pass, but then weakSelf could be nilled out before you actually try to use it. It would look something like
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage *tmpImage = [[UIImage alloc] initWithCGImage:asset.defaultRepresentation.fullScreenImage];
__strong __typeof__(weakSelf) strongSelf = weakSelf;
if ([strongSelf.scrollView viewWithTag:tagNumber] != nil){
dispatch_async(dispatch_get_main_queue(), ^{
__strong __typeof__(weakSelf) strongSelf = weakSelf;
if ([strongSelf.scrollView viewWithTag:tagNumber]!= nil){
UIImageView * tmpImageView = (UIImageView*)[strongSelf.scrollView viewWithTag:tagNumber];
tmpImageView.image = tmpImage;
}
});
}
});
Technically you don't need to do this in the first conditional in the background queue, because you're only dereferencing it once there, but it's always a good idea to store your weak variable into a strong variable before touching it as a matter of course.

If you need to render the image in UIImageView, you need to do this in main thread. It will not work unless you do it in main queue as shown in your code. Same is the case with any UI rendering.
if ([weakSelf.scrollView viewWithTag:tagNumber]!= nil){
UIImageView * tmpImageView = (UIImageView*)[weakSelf.scrollView viewWithTag:tagNumber];
tmpImageView.image = tmpImage;
}
As per Apple documentation,
Threading Considerations: Manipulations to your application’s user interface must occur on the main thread. Thus, you should always call
the methods of the UIView class from code running in the main thread
of your application. The only time this may not be strictly necessary
is when creating the view object itself but all other manipulations
should occur on the main thread.

Yes you need to use the main thread, since any UI changes needs to be done in the main thread.
As for using the GCD it is used to take the advantage of the Multi Cores on the device.
As for the strong and weak self
strong self: you might want a strong self, for in you code
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
(<your class> *) *strongSelf = weakSelf;
UIImage *tmpImage = [[UIImage alloc] initWithCGImage:asset.defaultRepresentation.fullScreenImage];
if ([strongSelf.scrollView viewWithTag:tagNumber] != nil){
dispatch_async(dispatch_get_main_queue(), ^{
if ([strongSelf.scrollView viewWithTag:tagNumber]!= nil){
UIImageView * tmpImageView = (UIImageView*)[strongSelf.scrollView viewWithTag:tagNumber];
tmpImageView.image = tmpImage;
}
});
}
});
say you have a view which make a API call and it takes time, so you switch back to another view but you still want the image to be downloaded then use strong since the block owns the self so the image is downloaded.
weak self: if in the above situation you dont want the image to download once you move to a different view then use weak self, since the block doesn't own any self.

If you will not use strongSelf in the code suggested by Kevin Ballard then it might lead to a crash because of weak getting nilled out.
Also a good practice would be to even check for strong being non nil at the point of creating
strongSelf = weakSelf
if(strongSelf)
{
// do your stuff here
}

Related

Trying to send process to background without UI lockup

I am trying to call a method in which I send to the background making use of dispatch_async.
It should be something that is simple, but for some reasons the UI is still blocked until the method returns.
Here is what I have:
dispatch_queue_t privateQueue = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(privateQueue, ^
{
__block UIImageView *imgView = [[UIImageView alloc] initWithFrame:self.view.frame];
dispatch_async(dispatch_get_main_queue(), ^
{
imgView = [controllerB startProcess];
controllerC.imageView = imgView;
});
});
I still have to wait for startProcess returns before UI is free again.
Then I tried to move imgView = [controllerB startProcess]; outside of dispatch_get_main_queue():
dispatch_async(privateQueue, ^
{
__block UIImageView *imgView = [[UIImageView alloc] initWithFrame:self.view.frame];
imgView = [controllerB startProcess];
dispatch_async(dispatch_get_main_queue(), ^
{
controllerC.imageView = imgView;
});
});
In this case, the UI is never updated with imgView but UI is not locked up.
I have tried to use a global queue, but the result is the same (UI has to wait):
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
I think I am missing something very obvious here. Either that or it has been long day for me.
EDIT:
In [controllerB startProcess];
I am making use of:
UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0.0);
UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I am not sure if these methods have anything to do with GCD that causes my problem. The image is just .png.
Been thinking hard on this. Am running out of ideas. The only way I can update the UI with the image is to place the method call within dispatch_get_main_queue(), which beats the purpose of using GCD because all UI is blocked until the image is ready and method returns.
Any suggestion would be greatly greatly appreciated.
Use the second approach. Modify startProcess to use completion blocks and update your imageView inside the completion block. This ensures that imageView is updated after startProcess is complete.
Is it possible, that -in your 2nd example- when you try to set the imageView on the main queue the asynchronous calculation of the imageView in the background has not finished yet, so it can't display the imageView?
In that case a dispatch group might help:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.name.queue”, DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
//Do work
imgView = [controllerB startProcess];
});
dispatch_group_notify(group,queue,^{
//This code runs as soon as the code in the dispatch group is done.
controllerC.imageView = imgView;
});

Crash when loading Core Data images into UITableViewCell on background thread

I have a series of profile pictures stored in Core Data as Binary Data (with the Allows External Storage option enabled, so don't jump on me for storing images in Core Data :)
Each image is being displayed in a UITableViewCell. At the moment there is a slight delay when the user taps to display the table view, presumably because it is loading them from Core Data which has a sufficient enough performance implication to lock-up the UI when loading them on the main thread.
I would like to put the image loading onto a separate background thread, so that the table view appears immediately and the images show when they have been loaded from Core Data.
I have tried the solution in this post with the following code in the -cellForRowAtIndexPath: method:
// Create the cell
MyCell *cell = [tableView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
// Get a reference to the object for this cell (an array obtained from Core Data previously in the code)
MyObject *theObject = [self.objectArray objectAtIndex:indexPath.item];
// Load the photo from Core Data relationship (ProfilePhoto entity)
UIImage *profilePhoto = [UIImage imageWithData:theObject.profilePhoto.photo];
// Set the name
cell.nameLabel.text = theObject.name;
dispatch_sync(dispatch_get_main_queue(), ^(void) {
// Set the photo
cell.photoImageView.image = profilePhoto;
});
});
// Return the cell
return cell;
However, the app crashes with the following error (repeated for each row in the table view):
CoreData: error: exception during fetchRowForObjectID: statement is still active with userInfo of (null)
Any help on understanding and resolving this issue would be appreciated.
The solution which Marcus Zarra provided gave me an idea about how to approach solving this problem, but I thought it would be worthwhile posting my implemented code for anyone else who comes to this question looking for a solution. So, here is my new code for the -cellForRowAtIndexPath: method:
// Create the cell
MyCell *cell = [tableView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
// Get a reference to the object for this cell
MyObject *theObject = [self.objectArray objectAtIndex:indexPath.item];
// Reset the image
cell.imageView.image = nil;
// Set the name
cell.nameLabel.text = theObject.name;
// Check if the object has a photo
if (theObject.profilePhoto != nil) {
// Create a new managed object context for the background thread
NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.parentContext = self.managedObjectContext;
// Perform the operations on the new context
[backgroundContext performBlockAndWait:^{
// Fetch the object
NSError *error;
ProfilePhoto *profilePhotoObject = (ProfilePhoto *)[backgroundContext existingObjectWithID:theObject.profilePhoto.objectID error:&error];
// Create the image
UIImage *thePhoto = [UIImage imageWithData:myObject.profilePhoto];
dispatch_async(dispatch_get_main_queue(), ^(void) {
// Set the photo
cell.imageView.image = thePhoto;
});
}];
}
return cell;
You also need to change the line in the App Delegate file which creates the managed object context from:
_managedObjectContext = [[NSManagedObjectContext alloc] init];
to
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
otherwise the .parentContext assignment will not work.
Hope this helps anyone else encountering this issue!
You are violating the threading rules of Core Data. A NSManagedObjectContext and all of its associated objects can only be accessed on the thread that created it (or the thread is is assigned to).
It appears you are accessing instances of NSManagedObject inside of that block which is on another thread and therefore violating the thread containment rules.
What are you trying to accomplish by moving these things onto a background queue?
Update
First, are you certain that the UIImage creation is the slow part? Have you profiled this code? How big are these images? Are they too big and the cost is in them being resized? Where is the real cost?
Second, the creation of the UIImage can be done on another thread but you cannot access a context from multiple threads. That is a hard line that you cannot cross with any degree of stability.
Third, I do not recommend storing images in Core Data. Storing binary data in a SQLite file is not recommended. I will assume that you are using the external record storage option.
The way around this, assuming that the loading and the creation of the UIImage is the actual problem would be to:
Create a context on the background queue (or create a private queue context)
Re-load the NSManagedObject from that context (you can pass the objectID around as it is thread safe.
Retrieve the image from this second instance of the NSManagedObject.
Continue with your code.
If this sounds like a lot of work you would be right! Hence the suggestion of profiling this code first and make sure what is actually slow and try and find out why it is slow. Simply loading and initializing a UIImage should not be that slow.
You're using the same managed object context on multiple queues, and as a result your app crashes.
When you call dispatch_get_global_queue you get a concurrent queue. When you queue up a bunch of blocks on that queue, they may run in parallel. In the block, you use Core Data objects that all come from the same managed object context-- which is not thread safe. This is pretty much a recipe for a crash.
What you should do instead is make use of NSManagedObjectContext queue confinement. Create the context using either NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType (private is better here since you're trying to get work off of the main queue). Then use performBlockAndWait to work with the Core Data objects. This would be something like
[self.context performBlockAndWait:^{
MyObject *theObject = [self.objectArray objectAtIndex:indexPath.item];
... etcetera ...
dispatch_async(dispatch_get_main_queue(), ^(void) {
// Set the photo
cell.photoImageView.image = profilePhoto;
});
}];
The blocks will execute sequentially, so there's no threading issue. They'll run on the context's private queue, which won't be the main queue. Note that I changed the UI update block to use dispatch_async above-- there's no reason to block the managed object context queue here by doing a synchronous call.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
The dispatch_get_global_queue gets you a background queue upon which you can dispatch background tasks that are run asynchronously (main idea is won't block your user interface).
But you are not allowed to perform user-interface process in the background, so the dispatch_async to the dispatch_get_main_queue lets that background queue dispatch the user interface updates back to the main queue.

EXC_BAD_ACCESS when using "freed" self in dispatch_async

I have a game written using the new SpriteKit in iOS7. I have a customised SKSpriteNode which would fetch and display a Facebook profile picture. However, since it may take some time to load the picture. I tried to load the picture in background when I initialised the node and display it only when the picture is loaded. Here is code snippet I wrote:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Code to load Facebook Profile picture
// ...
SKSpriteNode *fbFrame = [SKSpriteNode spriteNodeWithTexture:facebookPicTexture];
dispatch_async(dispatch_get_main_queue(), ^{
// self is my customised SKSpriteNode - so here I just add a sprite node of
// a facebook picture to myself as a child
[self addChild:fbFrame];
});
});
It works fine normally. However, if the loading of Facebook profile pic is slow, the user may have already switch to another screen when the picture is loaded. In such case, self will actually be removed from the scene hierarchy and no reference will be made to it.
When I read the block doc, I think the async block will retain self and so I presume it will still be valid when the main thread block is called. It turns out from time to time if the pic loading is really slow and the second dispatch_async is called when self is removed from the hierarchy, a bad access error will occur at the line [self addChild:fbFrame].
Am I understand the block memory management incorrectly? And is there a way to solve that kind of problem?
Your understanding of the memory management is correct, that the presence of self in this block will retain self until the dispatched block is completed. The solution is to not retain self while the block runs, by using a weak reference to self inside the block:
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do some stuff in background here; when done, then:
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf addChild:fbFrame];
});
});
You should review that block for any references to self (either explicit or implicitly by referencing an ivar directly), and replace them with weakSelf.
Or, (unlikely in this case) sometimes you'll see the weakSelf/strongSelf pattern where you must ensure that self isn't released during the execution of that nested block:
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do some stuff in background here; when done, then:
dispatch_async(dispatch_get_main_queue(), ^{
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
// stuff that requires that if `self` existed at the start of this block,
// that it won't be released during this block
}
});
});
By the way, the other approach is to cancel the network request when the view controller is dismissed. This requires a more significant change (using a NSOperation-based approach rather than GCD; or use NSURLSessionTask-based approach that allows you to cancel the request), but it is the other way to tackle this.
I think you need to write like this:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
objc_setAssociatedObject(self, #"yourTag", #"Alive", OBJC_ASSOCIATION_RETAIN);
dispatch_async(queue, ^{
// Code to load Facebook Profile picture
// ...
SKSpriteNode *fbFrame = [SKSpriteNode spriteNodeWithTexture:facebookPicTexture];
dispatch_async(dispatch_get_main_queue(), ^{
NSString *strAlive = (NSString *)objc_getAssociatedObject(self, #"yourTag");
if (strAlive)
{
// self is my customised SKSpriteNode - so here I just add a sprite node of
// a facebook picture to myself as a child
[self addChild:fbFrame];
}
});
});
dispatch_release(queue);
When you dont want the dispatch to continue procedure when the self (ViewController) is not visible anymore so write this:
objc_setAssociatedObject(self, #"yourTag", nil, OBJC_ASSOCIATION_RETAIN);
In:
- viewWillDisapear Or - viewDidDisapear
And renew calling the dispatch when you come back to than screen.

Which pattern to update interface using background thread?

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.

Returning image with threading in iOS

Hi I'm working through the Stanford iOS development class. I have a question regarding threading. I understand UIKit calls should be handled by the main thread. I was wondering if something like this is legal?
- (UIImage *)mapViewController:(MapViewController *)sender imageForAnnotation:(id<MKAnnotation>)annotation {
FlickrPhotoAnnotation *fpa = (FlickrPhotoAnnotation *) annotation;
NSURL *url = [FlickrFetcher urlForPhoto:fpa.photo format:FlickrPhotoFormatSquare];
__block UIImage *image;
dispatch_queue_t downloadQ = dispatch_queue_create("download queue", NULL);
dispatch_async(downloadQ, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
if (data) {
dispatch_async(dispatch_get_main_queue(), ^{
image = [UIImage imageWithData:data];
});
}
});
dispatch_release(downloadQ);
return image;
}
or I should just return NSData and handle all the threading in the calling method?
Thanks
Your code won't do what you want.
You are putting an asynchronous block into a queue, and then immediately returning from the method. You are not guaranteed that the block will actually run before you return -- in fact, the odds are that it won't.
So you'll return the current value of image. Since you didn't initialize it, it's probably garbage. Whoever calls this method will try to use a garbage pointer to an image, and (if you're lucky) crash. If you had initialized it to nil:
__block UIImage *image = nil;
that would be a little more polite.
The problem here is: your method must return a UIImage, so you must wait for the time it takes to make a fully constructed UIImage before you return. There is zero benefit to doing this on some other thread -- you're still waiting the same amount of time, and switching threads just adds overhead.
In order to load the image in a usefully asynchronous way, you need some way to asynchronously tell the caller when the image is done loading, via a callback to a delegate method or block. For example, look at NSURLConnection in Foundation. It has an older method that calls back via a delegate, and a newer method (+sendAsynchronousRequest:queue:completionHandler:) that calls back via a block.
I concur with CodaFi's comment. Actually, images can be created off the main thread. UIView creation and manipulation must be done on the main thread, but UIImage is not a UIView though. Furthermore, the runtime is likely to give you a warning or error if you try to manipulate the displaying UI on another thread (it did for me when I accidentally updated a UITableView on another thread).

Resources