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.
Related
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)];
});
}
}
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.
Importing multiple photos from album, one of the delegate method is
// Here info is array of dictionary containing FileName/AssetURL etc
- (void)somePicker(SomePicker*)somePicker didFinishPickingMediaWithInfo:(NSArray *)info {
_importStatusView.center = self.view.center;
[self.view addSubview:_importStatusView];
[self dismissViewControllerAnimated:YES completion:^{
NSNumber *total = [NSNumber numberWithInteger:info.count];
[info enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSDictionary *imageInfo = (NSDictionary*)obj;
NSString *fileName = [imageInfo objectForKey:#"UIImagePickerControllerFileName"];
NSURL *imageURL = [imageInfo objectForKey:UIImagePickerControllerReferenceURL];
ALAssetsLibrary *assetLibrary=[[ALAssetsLibrary alloc] init];
[assetLibrary assetForURL:imageURL resultBlock:^(ALAsset *asset) {
NSLog(#"start");
ALAssetRepresentation *rep = [asset defaultRepresentation];
Byte *buffer = (Byte*)malloc(rep.size);
NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:rep.size error:nil];
NSData *data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
NSString *filePath = [_currentPath stringByAppendingPathComponent:fileName];
[data writeToFile:filePath atomically:YES];
//This also has no effect
//dispatch_async(dispatch_get_main_queue(), ^{
//_lblImportCountStatus.text = [NSString stringWithFormat:#"%d of %d",idx+1,[total integerValue]];
//NSLog(#"Label value->%#",_lblImportCountStatus.text); //This prints values but after everything is finished it prints all line at once i.e. at the end of the enumeration of all items
//});
//Update UI
NSNumber *current = [NSNumber numberWithInteger:idx+1];
NSDictionary *status = [NSDictionary dictionaryWithObjectsAndKeys:current,#"current", total,#"totalCount", nil];
[self performSelectorOnMainThread:#selector(updateImportCount:) withObject:status waitUntilDone:YES];
//_lblImportCountStatus.text = [NSString stringWithFormat:#"%d of %d",idx+1,[total integerValue]];
if(idx==info.count-1){
[_importStatusView removeFromSuperview];
}
NSLog(#"Finish");
} failureBlock:^(NSError *error) {
NSLog(#"Error: %#",[error localizedDescription]);
}];
}];
}];
}
My declaration for status view and label is
#property (strong, nonatomic) IBOutlet UIView *importStatusView; //View containing label
#property (weak, nonatomic) IBOutlet UILabel *lblImportCountStatus; //Label
Everything in above code is working fine and as expected, but the problem is a importStatusView is being added to the screen but lblImportCountStatus value is not displaying, though If I log the values it shows updated.
When enumeration is finished at the end all the NSLog gets printed for e.g. If I have imported 10 photos than at last it prints, i.e. dispatch_async(dispatch_get_main_queue() this function has no effect at all while enumeration is in progress.
Label value->1 of 10
Label value->2 of 10
Label value->3 of 10
Label value->4 of 10
Label value->5 of 10
Label value->6 of 10
Label value->7 of 10
Label value->8 of 10
Label value->9 of 10
Label value->10 of 10
What could be the issue ?
Update:
-(void)updateImportCount:(NSDictionary*)info{ //(NSNumber*)current forTotalItems:(NSNumber*)totalCount{
NSNumber *current = [info objectForKey:#"current"];
NSNumber *totalCount = [info objectForKey:#"totalCount"];
_lblImportCountStatus.text = [NSString stringWithFormat:#"%d of %d",[current integerValue],[totalCount integerValue]];
[_lblImportCountStatus setNeedsDisplay];
NSLog(#"Updating ui->%#",_lblImportCountStatus.text);
}
Above function works on main thread and updates but stil label is not shown it prints following NSLog
start
Updating ui->1 of 10
Finish
start
Updating ui->2 of 10
Finish
start
Updating ui->3 of 10
Finish
start
Updating ui->4 of 10
Finish
start
Updating ui->5 of 10
Finish
start
Updating ui->6 of 10
Finish
start
Updating ui->7 of 10
Finish
start
Updating ui->8 of 10
Finish
start
Updating ui->9 of 10
Finish
start
Updating ui->10 of 10
Finish
I have uploaded project at this location, please feel free to help.
All those blocks are executing on the main thread (the easiest way to verify this is using NSThread's +currentThread to get the current thread, and -isMainThread to check if it's the main thread. Anywhere you have code that you want to see what thread it's on, do something like this:
NSLog( #"enumeration block on main thread: %#",
[[NSThread currentThread] isMainThread] ? #"YES" : #"NO" );
I think the problem is, since this is all executing on the main thread, you're locking up the runloop, not giving the UI a chance to update.
The right way to fix this is probably to really do this processing on a separate thread (calling the code to update the UI via performSelectorOnMainThread:, as you're doing now). But, a quick hack to make it work would be to allow the runloop to run. At the end of updateImportCount:, do something like this:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
It ain't pretty, but it will work.
Update:
In Cocoa (Mac OS and iOS) there a concept of a runloop. The main thread of your application drives an NSRunLoop which serves as the event loop. User actions -- taps, etc. -- are processed through this loop, as are things like timers, network connections, and other things. See the NSRunLoop Reference for more information on that.
In iOS, drawing also happens on the runloop. So when you call setNeedsDisplay on a view, that view is not redrawn immediately. Rather, it's simply flagged as needing redraw, and then on the next drawing cycle (the next trip through the runloop) the actual drawing takes place. The UIView reference has a brief description of this (see the section "The View Drawing Cycle". Quoting from that section:
When the actual content of your view changes, it is your
responsibility to notify the system that your view needs to be
redrawn. You do this by calling your view’s setNeedsDisplay or
setNeedsDisplayInRect: method of the view. These methods let the
system know that it should update the view during the next drawing
cycle. Because it waits until the next drawing cycle to update the
view, you can call these methods on multiple views to update them at
the same time.
When you return from whatever method was called in response to some user action (in this case, somePicker:didFinishPickingMediaWithInfo:, control returns to the runloop, and any views that need redrawing, are. However, if you do a bunch of processing on the main thread without returning, the runloop is basically stalled, and drawing won't take place. The code above basically gives the runloop some time to process, so drawing can take place. [NSDate date] returns the current date and time, right now, so that basically tells the runlooop "run until right now", which ends up giving it one cycle through the loop, giving you one drawing cycle, which is an opportunity for your label to be redrawn.
I am working on an app that needs to flip through 300 or so images in sequence, with a 2 second delay between images.
These images are barcodes that are generated on the fly during the display. They are not stored images.
The display is in a navigation controller and I would like the user to be able to click the 'back' button without the app crashing from a selector being sent to an instance that no longer exists.
I know that it is possible to animate a UIImageView, but I don't want to create an large array of images because of memory issues.
I'd like to do this in a loop where I generate the barcode, display the image, delay 2 seconds, and then repeat with the next image.
The following code works, but crashes if you click the 'back' button, with a 'message sent to deallocated instance' error.
NSSet *dataSet = [self fetchDataSet];
for (MyBarCode *data in dataSet) {
// display barcode in UIImageView
[self updateBarCodeImage:data ];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 2.0]];
}
There doesn't seem to be a way to cancel this timer, or I could do that in viewWillDisapear.
What's the right way to do this?
Please don't just point me to the animationImages examples. I've already seen all of them and -- again -- I don't want to have to hold all these images in memory during the animation. If there's a way to generate the images on the fly during animation, now that would be interesting.
I think something like this should work:
__weak ViewController *bSelf = self;
NSSet *dataSet = [self fetchDataSet];
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(myQueue, ^{
__strong ViewController *sSelf = bSelf;
for (BoardingPass *data in dataSet) {
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[sSelf updateBarCodeImage:data]
});
}
});
UPDATED:
Well I am not sure what exactly you are looking for. If you are talking about a simple animation than yes, Cocos 2D would be overkill. In that case I suggest doing this tutorial which gave me a lot of ideas on a project I was working on some time back:
http://www.raywenderlich.com/2454/how-to-use-uiview-animation-tutorial
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).