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).
Related
I am relatively new to IOS programming.
I have a NSObject "Places" which is given below.
#interface Places : NSObject
#property(strong) NSString *placeName;
#property(strong) UIImage *placeImage;
#end
I am listing the array of Places objects in UITableView. On tableView:cellForRowAtIndexPath: images are fetching asynchronously from web urls. On tableView:didSelectRowAtIndexPath:, I pass the respective 'Places' object to another ViewController (detailViewController). In that detailViewController, I uses below code to display the image in a UIImageView.
self.imageView.Image = self.myPlaces.placeImage
My problem is when I pass that object, the image needn't be fetched to placeImage. Is there anyway to update the self.imageView on successive completion of image fetching to placeImage in mainViewController.
My code is given below. It is called in tableView:cellForRowAtIndexPath: of mainViewController.
NSURL *url = [NSURL URLWithString:#"http://imageurl_goes_here"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *imageData = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
if(imageData != nil)
{
place.placeImage = [UIImage imageWithData: imageData];
cell.cellImageView.image = place.placeImage;
}
});
});
Well, there are some techniques to archive that:
You can notify your second view controller every time you received image. E.g. in your inner dispatch_async. But this requires main controller to know much about second one.
You can use Key-Value Observing technique to observe every place's image updating. In this case you might also want to use some handy library that will do sanity for you (like unsubscribing when object is deallocated, otherwise you will get crash) like that one or any other.
This post of Matt Thompson may be helpful on other types of communications between instances.
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.
How would you stall the program from executing? Doesn't sleep function do that? If it does, what's wrong with this code:
AsyncImageView *tmpImage = [imagesArray objectAtIndex:index];
int waitCounter = 0;
while (waitCounter < 10 && !tmpImage.imageIsLoaded) {
waitCounter++;
sleep(0.5);
}
NSLog(#"%i",waitCounter);
if (tmpImage.imageIsLoaded) {
return [tmpImage image];
} else {
return [UIImage imageNamed:#"img_noimg.png"];
}
The AsyncImageView class loads an image from an URL then sets imageIsLoaded property to true. This usually happens quite fast (under a second), but if I scroll though the images like a madman, then it doesn't have time to load (it loads 10 images ahead).
I've added the while to prevent that from happening, but the log displays multiple cases of waitCounter being 10 while there was no sleep time (the interface didn't freeze).
One approach to solve a problem like this is to use a completion handler. A completion handler signals the call-site when an asynchronous task is complete and passes the result of the task as a parameter to the completion handler.
Your AsyncImageView class does have indeed such a completion handler. It's a selector. Today, one would use a block instead. The principle is the same, though.
Continuing after an async task has finished is called "Continuation". With blocks, you code looks as follows:
typedef void (^completion_handler_block)(id result);
void doSomethingAsync(completion_handler_block)completionHandler;
- (void) foo {
doSomethingAsync(^(id result) {
// Continuation code
...
});
}
With blocks implementing continuation is especially easy, since it keeps the context (referenced variables defined in the call-site) within the block (closure).
Where are you doing this? in method cellForRowAtIndexPath ?
If so, you must not sleep here.
For loading an image asynchronously you must
1) pass the real UIImageView to your AsyncImageView and
2) when you load the UIImage from URL, instead of set *_imageIsLoaded = TRUE*, you can load it directly in the passed UIImageView using *_imageView.image = image*
For instance you can do something like this, taking into account that maybe you already have the image loaded, etc. Create this method in AsyncImageView like this
-(void) reloadWithImageView:(UIImageView*)imageView {
_imageView = imageView;
}
and when you later load the UIImage (probably in the connectionDidFinishLoading method)
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
connection = nil;
UIImage *image = [UIImage imageWithData:data];
_imageView.image = image;
}
Try This, I guess it will work. Instead of
sleep(0.5);
try
[NSThread sleepForTimeInterval:.5f];
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
}
I have an app i am building it works fine but the image source i am using is from a website and when I switch back to my initial view it takes quite some time for it to load. My question is would there be a way to get this done and have the speed be faster.
here is the code I use to pull my image source
////Loads UIImageView from URL
todaysWallpaper.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://www.inkdryercreative.com/daily/archive/mondays/images/062-mondays-960x640-A.jpg"]]];
any help or a shove in the proper direction. Soon the image will change every time day as well so any help/thoughts on that would be of great appreciation.
The probleme here is that dataWithContentsOfURL: is on the main thread, so it will block your UI when the image is downloaded.
You have to download it asynchronously. For that I personally use a great piece of code i found on the internet : SDWebImage. It does exactly what you want.
wrap the UIImage creation in a block running asynchronously (code assumes ARC) which in turn calls your callback in the main thread
#implementation Foo
...
Foo* __weak weakSelf=self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:
[NSURL URLWithString:#"http://www.inkdryercreative.com/..jpg"]]];
dispatch_sync(dispatch_get_main_queue(),^ {
//run in main thread
[weakSelf handleDelayedImage:image];
});
});
-(void)handleDelayedImage:(UIImage*)image
{
todaysWallpaper.image=image;
}
the weakSelf trick ensures that your Foo class is properly cleaned up even if the URL request is still running