1

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!

Rick
  • 61
  • 1
  • 4

3 Answers3

0

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) {
  ...
}];
Abhi Beckert
  • 32,787
  • 12
  • 83
  • 110
0

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);
}];
0

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];
                ...


            });
        }             
    }];

}
CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • Thanks for your answer CD. It's very informative/helpful. One clarification. Do I put the typedef line in the interface section of my view controller? And is the next line from your comment supposed to go in my Luminosity class .h file? – Rick Apr 05 '14 at 08:29
  • The typedef is for convenience. The prototype of the async method AND the typedef for the completion handler belong _together_ (that is, when the declaration of the async method is in the interface section, put the typedef at top if the interface as well, otherwise leave it in the implementation file). Note: you can declare the async method also _without_ using a typedef for the completion handler, as shown in many Apple APIs. – CouchDeveloper Apr 05 '14 at 08:35
  • Note: usually, you *define* methods in the @implementation section *without* the need to declare them. If you have the desire to declare a private method in the implementation, you usually use "class extension" interface or a Category in the implementation file. – CouchDeveloper Apr 05 '14 at 08:39
  • I would discourage the use of `typedef`s since it makes the code less obvious locally and prevents proper auto completion magic. –  Apr 05 '14 at 08:42
  • dispatch_after() is not an asynchronous method. Have a look again, it's executing on the main queue. – Abhi Beckert Apr 05 '14 at 08:48
  • @b52 I don't see differences in autocompletion support when using a typedef (both will be autocompleted as expected). Otherwise I do think it's personal preference. – CouchDeveloper Apr 05 '14 at 08:59
  • @AbhiBeckert Of course, `dispatch_after` is asynchronous ;) It's asynchronous since its "effect" will not be visible to the call-site when it returns. If it were synchronous, it's "effect" must be realized *before* it returns and visible to the call-site (on the same execution context) immediately after returning. – CouchDeveloper Apr 05 '14 at 09:06
  • @CouchDeveloper You obviously have a different definition of asynchronous to me. My definition says it means something happens simultaneously with something else. Which will only happen if you choose send the block to a queue other than the current one. – Abhi Beckert Apr 05 '14 at 09:56
  • @AbhiBeckert It's not *my* definition. You can look up this on wiki. You can also use `dispatch_async` where the queue is the same where this statement will be executed, and still the block gets executed _asynchronously_ - as the function name indicates. If two tasks executes _simultaneously_ this is also called _concurrently_. – CouchDeveloper Apr 05 '14 at 13:23
  • @CouchDeveloper dispatch_async executes asynchronously, dispatch_sync executes synchronously, dispatch_after executes asynchronously or synchronously depending what queue you send to it. In this case it's executing synchronously. GCD does not use the term "concurrent" anywhere, it uses asynchronous and synchronous. – Abhi Beckert Apr 05 '14 at 18:53
  • @AbhiBeckert No Abhi, `dispatch_after` *always* executes asynchronously - even when the delay is zero, and the queue is the current execution context. You can check that yourself with a three line program ;) One should not confuse asynchrony with concurrency. Asynchrony is also independent on the execution context (a single threaded system can very well execute methods in an asynchronous fashion, e.g. JavaScript). – CouchDeveloper Apr 06 '14 at 07:53