How to wait for methods with result/completion blocks to finish?
-
This is on iOS... Say I have use a method that has some kind of
result/completion block like ALAssetsLibrary assetForURL In the
example below, in the assetForURL result block, the JPEG
representation of the asset is read into a buffer and the NSData form
of that buffer is assigned to a property. I need the property to be
set before my code continues past his point. How do I go about
accomplishing that? Am I forced to show a "waiting for xxx" view until
this finishes?
I tried surrounding the assetForURL call with a semaphore but this
does not work on the main app thread (the dispatch_semaphore_wait()
blocks the main thread and thus the dispatch_semaphore_signal() in the
resultBlock never gets to run to signal that the block is done).
I guess in general I an wondering how I correctly wait for things to
happen that I absolutely positively need to be done before I
continue...
Thanks in advance...
Chris
* * *
-(void)getJPEGFromAssetForURL:(NSURL *)url {
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL:url
resultBlock: ^(ALAsset *myasset) {
ALAssetRepresentation *rep = [myasset defaultRepresentation];
Byte *buf = malloc([rep size]);
NSError *err = nil;
NSUInteger bytes = [rep getBytes:buf fromOffset:0LL
length:[rep size] error:&err];
if (err || bytes == 0) {
[...]
}
self.imageJPEG = [NSData dataWithBytesNoCopy:buf
length:[rep size] freeWhenDone:YES];
}
failureBlock: ^(NSError *err) {
NSLog(@"can't get asset %@: %@", url, err);
}];
[assetslibrary release];
} -
On Mar 7, 2011, at 9:00 PM, Chris Markle <cmarkle...> wrote:
> I guess in general I an wondering how I correctly wait for things to
> happen that I absolutely positively need to be done before I
> continue...
You rework your approach to not require these things to be complete before your code continues.
Throwing up a modal "please wait" UI until the callback gets executed is a common approach whenever the user can't logically expect to proceed until the app is done with some processing. An asynchronous API is preferable because it lets you return to the runloop and process user events, rather than get stuck in a busy loop that is indistinguishable to the OS from a hung process.
--Kyle Sluder -
Matt Neuburg Re: How to wait for methods with result/completion blocks to finish? Mar 08 2011, 18:06On Mon, 07 Mar 2011 21:00:33 -0800, Chris Markle <cmarkle...> said:
> This is on iOS... Say I have use a method that has some kind of
> result/completion block like ALAssetsLibrary assetForURL In the
> example below, in the assetForURL result block, the JPEG
> representation of the asset is read into a buffer and the NSData form
> of that buffer is assigned to a property. I need the property to be
> set before my code continues past his point. How do I go about
> accomplishing that?
You put "my code" in the completion block. That's what a completion block *is*: it's where you do the stuff that depends upon this operation having completed. See also the docs (Preparing an Asset for Use):
> To load a value for one or more properties, you invoke loadValuesAsynchronouslyForKeys:completionHandler:. *In the completion handler*, you take whatever action is appropriate depending on the property's status.
And after all, as that same paragraph in the docs tells you (along with the subsequent example), the attempt to get the property's value might also fail. The only sensible place to handle all the things that can happen is the completion block.
Sure, this means you have to break up your procedure into steps, but block syntax can actually help you to clarify this. m.
--
matt neuburg, phd = <matt...>, <http://www.apeth.net/matt/>
A fool + a tool + an autorelease pool = cool!
Programming iOS 4!
Chris Markle Re: How to wait for methods with result/completion blocks to finish? Mar 08 2011, 22:44Matt, Kyle,
Thanks for the guidance and pointers to other doc. I am having trouble
wrapping my head around the asynch patterns, especially since I am
working with a code base that I inherited that is not doing a good job
of leveraging these patterns.
There are two cases (at least) in that app (which uploads images and
videos from the Photo Library or Camera using a proprietary protocol)
where it seems like we need to "wait". Maybe you can help me mentally
refactor these two...
1. Camera used to take picture or video (UIImagePickerController).
Want to save image that was taken to the camera roll like happens with
the camera app. That saving process starts when the user returns from
the camera. If they then go to the Photo Library (again with
UIImagePickerController), we don't let them do that until the image is
fully saved since we want them to be able to see them image in the
library that they just shot. So when they tap the "photo library"
button, we (as Kyle said) "throwing up a modal 'please wait' UI" until
the save to photo library function is completed, at which point we
show the photo library.
2. When all the picture taking or choosing from the library choosing
has happened, and the user ultimately has picked an image/video to
upload, they press "upload". If it's a video, we have the file already
that we need to transfer. But if it's an image we would like to get
the JPEG representation (via ALAsset / ALAssetRepresentation) into a
file so that we can transfer that file (the uploader system is
file-based). That's where some form of that code with the resultBlock
from my original post comes into play. It has the resultBlock that
returns the asset and I need to wait for that block to complete so
that I have the JPEG representation available that I can then put into
a file to transfer.
Any thoughts about how I can adjust my thinking and the app's design
to deal with these cases would help me understand this better.
Thanks in advance.
ChrisMatt Neuburg Re: How to wait for methods with result/completion blocks to finish? Mar 09 2011, 02:45On Mar 8, 2011, at 1:44 PM, Chris Markle wrote:
> 1. Camera used to take picture or video (UIImagePickerController).
> Want to save image that was taken to the camera roll like happens with
> the camera app. That saving process starts when the user returns from
> the camera. If they then go to the Photo Library (again with
> UIImagePickerController), we don't let them do that until the image is
> fully saved since we want them to be able to see them image in the
> library that they just shot. So when they tap the "photo library"
> button, we (as Kyle said) "throwing up a modal 'please wait' UI" until
> the save to photo library function is completed, at which point we
> show the photo library.
I'll just use this as an example of how my own thinking runs when faced with this sort of situation. I confront the matter in terms of MVC:
* The actual saving of the image into the camera roll is model - this is your data. If you use UIImageWriteToSavedPhotosAlbum you can arrange to be notified asynchronously by setting the completionTarget, completionSelector, and optionally the contextInfo. The iOS 4 calls with completion handler are parallel.
* Doing something in response to the situation with the model is the job of the controller. So, to communicate between the two, I'd use a notification (from the model) just before we start to save and another notification (from the model) when the save is completed. The controller's job is then to maintain state; you could have an ivar, photoLibraryUnavailable, and set it to YES in response to the first notification and to NO in response to the second.
* The problem of what the user should see is view, but the controller is in charge of it. Presumably the controller will respond to the two notifications appropriately. The question is then what's appropriate. That's a design decision; many opinions are valid. Personally I would shun the notion you express:
> when they tap the "photo library"
> button, we (as Kyle said) "throwing up a modal 'please wait' UI"
Rather, I'd ask myself why the user is able to tap the photo library button at all, if the photo library is not available. Don't make the user wrong by providing a button and then punishing the user for tapping it. The button should instead disappear, perhaps, or become inactive, or be replaced by a spinning activity indicator. This is why we're maintaining state in the controller; at any given moment, the interface should reflect the state of things and the range of possible steps that the user can perform, and the controller can consult its state to make it so. Even if you were to switch temporarily to a modal view that just says Saving, that's better than having a button that slaps the user's hand. That's my philosophy, anyway... But this is no longer an architectural matter, but a sheer problem of philosophy and how you want the user to feel about your app. m.
--
matt neuburg, phd = <matt...>, http://www.apeth.net/matt/
pantes anthropoi tou eidenai oregontai phusei
Among the 2007 MacTech Top 25, http://tinyurl.com/2rh4pf
Programming iOS 4! http://www.apeth.net/matt/default.html#iosbook
RubyFrontier! http://www.apeth.com/RubyFrontierDocs/default.html
TidBITS, Mac news and reviews since 1990, http://www.tidbits.comOn Tue, Mar 8, 2011 at 5:45 PM, Matt Neuburg <matt...> wrote:
>> when they tap the "photo library"
>> button, we (as Kyle said) "throwing up a modal 'please wait' UI"
>
> Rather, I'd ask myself why the user is able to tap the photo library button at all, if the photo library is not available. Don't make the user wrong by providing a button and then punishing the user for tapping it. The button should instead disappear, perhaps, or become inactive, or be replaced by a spinning activity indicator. This is why we're maintaining state in the controller; at any given moment, the interface should reflect the state of things and the range of possible steps that the user can perform, and the controller can consult its state to make it so. Even if you were to switch temporarily to a modal view that just says Saving, that's better than having a button that slaps the user's hand. That's my philosophy, anyway... But this is no longer an architectural matter, but a sheer problem of philosophy and how you want the user to feel about your app. m.
Allowing the user to perform an action that requires some work isn't
punishing the user. Now allowing them to cancel the operation without
a good reason is punishing the user.
There's no reason to load all the camera roll contents before the user
requests the camera roll. Let the user tap the button and throw up a
*non-modal* progress indicator that still allows them to back out of
the camera roll if they don't want to wait. If they haven't backed out
by the time the background loading is complete, take away the progress
indicator and show them the contents.
--Kyle SluderOn Tue, Mar 8, 2011 at 6:13 PM, Kyle Sluder <kyle.sluder...> wrote:
> Allowing the user to perform an action that requires some work isn't
> punishing the user. Now allowing them to cancel the operation without
> a good reason is punishing the user.
>
Of course, I meant to say "not allowing."
--Kyle SluderChris Markle Re: How to wait for methods with result/completion blocks to finish? Mar 09 2011, 19:18Matt, Kyle,
Thanks for taking your time to give me your thoughts on this. It's
good food-for-thought and "thought" is what I need now... Thanks!
Chris


