I have the following code to a run method that returns a UIImage made from a UIView that's created in separate UIView class file. The FlagMarkerView is a separate subclass of UIView and is referenced here. The problem is the line FlagMarker *markerView... throws the following Main Thread Checker Error and crashes the app.
UIView.init(coder:) must be used from the main thread.
The code used to work as is, but no longer works as I've updated the project to target iOS 11.
I tried wrapping the FlagMarkerView call in a dispatch_async(dispatch_get_main_queue(), ^{}); but that doesn't work because it doesn't get picked up by the return UIImage in the method. I also tried to use a -(void) method instead of returning a UIImage, but that’s hazard with the complexity of my project.
Is there a way I can create the newMarkerImage in - (void)updateMyFlagsWitAlert: and use dispatch_async(dispatch_get_main_queue(), ^{ for FlagMarkerView so newMarkerImage can be created in line from markerImage.
- (void)updateMyFlagsWitAlert:(BOOL)isAllowed{
for (AGSGraphic *graphic in weakSelf.flagOverlay.graphics) {
FlagModel *flagToUpdate = graphic.attributes[#"flag"];
UIImage *newMarkerImage =
[weakSelf markerImageForFlag:flagToUpdate withDetail:detail];
}
}
- (UIImage *)markerImageForFlag:(FlagModel *)flag withDetail:(BOOL)withDetail {
// This line crashes app
FlagMarkerView *markerView = [[[NSBundle mainBundle] loadNibNamed:#"FlagMarkerView" owner:self options:nil] objectAtIndex:0];
[markerView setFlag:flag];
UIImage *markerImage = [markerView imageWithDetail:withDetail];
[markerView layoutIfNeeded];
return markerImage;
}
You should probably redesign your code to be async, but the quickest way I could think of is something like:
__block FlagMarkerView *markerView;
dispatch_sync(dispatch_get_main_queue(), ^{
markerView = [[[NSBundle mainBundle] loadNibNamed:#"FlagMarkerView" owner:self options:nil] objectAtIndex:0];
[markerView setFlag:flag];
UIImage *markerImage = [markerView imageWithDetail:withDetail];
[markerView layoutIfNeeded];
});
return markerImage;
This will make your code run in the main thread, and wait for it to finish.
It is not a good design, you should probably design your code better and not rely on a return value.
Instead, your code could use a block as a parameter that will receive the result after it was processed in the main thread, instead of waiting for it using a semaphore.
But sometimes, if you just need something to work - this solution could be helpful.
The following code-change solved my problem. I found the problem is not with markerImageForFlag at all even though the Main Thread Checker highlighted the markerView. I believe the weakSelf reference put the task on the background thread, which was ok for awhile, but doesn't work anymore. All I had to do is put the dispatch_sync, not dispatch_async, on the call, newMarkerImage. Thanks to #Rob and #Moshe! See below.
- (void)updateMyFlagsWitAlert:(BOOL)isAllowed{
for (AGSGraphic *graphic in weakSelf.flagOverlay.graphics) {
FlagModel *flagToUpdate = graphic.attributes[#"flag"];
__block UIImage *newMarkerImage = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
newMarkerImage = [weakSelf markerImageForFlag:flagToUpdate withDetail:(scale < markerThreshold)];
});
}
}
Related
I'm trying to get my head around the dispatch mechanism of Objective-C. But don't succeed.
I have these two methods:
- (void)downloadImage
{
NSString * URLString= #"http://www.something.com";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
NSURL *imageURL = [NSURL URLWithString:fullURL];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
theImportant.image = [UIImage imageWithData:imageData];
dispatch_sync(dispatch_get_main_queue(), ^{
stopAnimate=true;
[self MoveToPosition:myView.center];
});
});
}
and:
-(void)MoveToPosition:(CGPoint)position{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[UIView animateWithDuration:1.5
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut |UIViewAnimationOptionBeginFromCurrentState
animations:^{
myView.center=position;
}
completion:^(BOOL finished){
if (stopAnimate){
//do Nothing
} else if (someFlag){
[self downloadImage];
}
}
];
}
So, the first method is called by the second, which in turn is calling the first.
This is working all fine, except for the fact that somehow theImportant.image is not displayed.
When I move the
theImportant.image = [UIImage imageWithData:imageData];
within the
dispatch_sync
block of downloadImage, the animation starts behaving in an unexpected way.
I do realize that I don't completely understand the dispatching, but I hope that your insights will grow my wisdom.
theImportant is defined as
#property (strong, atomic) IBOutlet UIImageView * theImportant;
My question in short is: why is my theImportant.image not displayed.
dispatch_xxx... methods do not call anything. They take a block of code and add it to a queue. It's the same as if you wrote some instructions on a piece of paper and put that piece of paper on the desk of a co-worker. Your co-worker will finish whatever he's been doing, and when he is done, he will do what is written on the paper.
There is one special queue, called the "global queue". If you put a piece of paper on the "global" desk, there are five dozen co-workers, and any of them could pick up that piece of paper at any time, so jobs will be done faster (because many people work on the different tasks) but you never know in which order they are done.
I ended up adding
#property (strong, atomic) UIImage * theImportantImage;
to my .h file, setting the image to that, and then I did
theImportantFace.image=theImportantImage.
Not sure why that would work, but hey, it does. It seems my problem was not related to async stuff at all. Pity. Unsatisfactory not to find out what is the issue.
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;
});
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
}
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).