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
  • On 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!
    Matt, 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.

    Chris
  • On 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.com
  • On 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 Sluder
  • On 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 Sluder
  • Matt, 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
previous month march 2011 next month
MTWTFSS
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
Go to today