Question

Very new to Objective-C and having a rough time figuring out how to accomplish the following. I come from a javascript background, so I may not be approaching this in the correct way.

In my view controller I'm making a call to a class method getLuminosity. I want to collect some float's from the camera for 7 seconds, average them, and then return that average but have no clue how to do it. Here is my getLuminosity code:

- (CGFloat) getLuminosity {

    ...

    [vidCam startCameraCapture];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [vidCam stopCameraCapture];
        NSNumber *averageLumin = [_arrayOfLumins valueForKeyPath:@"@avg.self"];
        return [averageLumin floatValue];
    });

    return floatFromDispatchBlock;

}

Thanks for any help!

Was it helpful?

Solution

Your method getLuminosity invokes an asynchronous method. This inevitable makes the calling method asynchronous as well!

So, the first step to a working approach is to realize that your method getLuminosity is asynchronous, and an asynchronous method SHOULD provide a means to signal the call-site that the underlying asynchronous task is complete:

We might use a "completion handler" to accomplish this, but keep in mind that this is not the only way to achieve this (see @b52 answer how to accomplish this with promises).

The completion handler - a Block to be more precise - needs to be defined (that is, implemented) by the call-site. When the call-site "starts" the asynchronous task (through invoking the asynchronous method) the completion handler gets passed through to the asynchronous task. The task's responsibility is to "call" the completion handler when it is finished.

Usually, the completion handler has parameters which will be used to transfer the result of the asynchronous task. For example, we could define a completion block as follows:

typedef void (^completion_t)(NSNumber* averageLumin, NSError* error);

Note: usually, the "type" of the completion handler will be defined by the underlying asynchronous task, respectively its requirements. The implementation of the block, though, will be provided by the call-site.

Your asynchronous method can then look like this:

- (void) luminosityWithCompletion:(completion_t)completion;

And can be implemented:

- (void) luminosityWithCompletion:(completion_t)completion 
{
    ...

    [vidCam startCameraCapture];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [vidCam stopCameraCapture];
    NSNumber *averageLumin = [_arrayOfLumins valueForKeyPath:@"@avg.self"];

    if (completion) {
        completion(averageLumin, nil)
    }
});

On the call-site, you use a "continuation" to do whatever is necessary when the result is eventually available:

- (void) foo
{        
    ...
    // Define the "continuation" with providing the completion block:
    // Put *everything* that shall be executed *after* the  result is
    // available into the completion handler:
    [self luminosityWithCompletion:^(NSNumber* result, NSError*error) {
        if (error) {
            // handle error
        }
        else {
            // continue on the main thread:
            dispatch_async(dispatch_get_main_queue(), ^{
                // using "result" on the main thread
                float lum = [result floatValue];
                ...


            });
        }             
    }];

}

OTHER TIPS

dispatch_after is the same as setTimeout in javascript. You can't return the contents of either one.

You've got two options, either put the current thread to sleep for 7 seconds (the processor will use the CPU core for something else for 7 seconds, then come back to it):

- (CGFloat) getLuminosity {

    ...

    [vidCam startCameraCapture];

    [NSThread sleepForTimeInterval:7.0];

    [vidCam stopCameraCapture];
    NSNumber *averageLumin = [_arrayOfLumins valueForKeyPath:@"@avg.self"];
    return [averageLumin floatValue];

}

Or provide a callback block:

- (void) getLuminosityWithCallback:(void (^)(CGFloat averageLumin))callback; {

    ...

    [vidCam startCameraCapture];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [vidCam stopCameraCapture];
        NSNumber *averageLumin = [_arrayOfLumins valueForKeyPath:@"@avg.self"];

        callback(averageLumin.floatValue);
    });

}

[obj getLuminosityWithCallback:^(CGFloat averageLumin) {
  ...
}];

There are basically two ways. The first one would be to implement the getLuminosity method in a blocking synchronize fashion, which is not a good idea. The second would be to use asynchronous patterns, e.g. using delegates, blocks or promises (shameless self-promotion, although there are much more implementations available).

Since you have a background in JS, I assume you are familiar with the term promise. The following snippet shows how you would accomplish such task using promises in Objective-C:

#import <OMPromises/OMPromises.h>

- (OMPromise *)getLuminosity {
    OMDeferred *deferred = [OMDeferred deferred];

    [vidCam startCameraCapture];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [vidCam stopCameraCapture];
        NSNumber *averageLumin = [_arrayOfLumins valueForKeyPath:@"@avg.self"];
        [deferred fulfil:averageLumin];
    });

    return deferred.promise;
}

And you use it this way:

[[self getLuminosity] fulfilled:^(NSNumber *lumin) {
    NSLog(@"%@", lumin);
}];
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top