1

I have a delegate callback method that needs to return data(Ex NSArray) to the caller.

I can get this data from another block call. Can anyone please help me with a design to use blocks in this case.

I came up with something like this but not sure if this is the right way to go about doing this.

NSArray* (^eventsForDate)(NSDate *) = ^(NSDate *date) {
    [[DataManager sharedInstance] getEventsForDate:date onSuccess:^(NSArray *events) {
        return events; //Obviously this doesn't work. Need help here.
    } onError:^(NSError *error) {
        return @[];
    }];
};

//Delegate call back
- (NSArray *) calendarEventsForDate:(NSDate *) date
{
    return eventsForDate(date);
}
Yuchen
  • 24,092
  • 18
  • 133
  • 193
i_raqz
  • 2,789
  • 10
  • 48
  • 86
  • There are [a couple](http://stackoverflow.com/questions/9474018/returning-uiimage-from-block) of [other questions](http://stackoverflow.com/questions/10742885/returning-nsdictionary-from-async-code-block) which should help. You can't return anything from a block, so you'll just have to call `eventsForDate` in the completion handlers. – Petesh Feb 01 '15 at 19:28
  • You can of course return something from a block. It will be returned to whoever called the block. In this case, somewhere inside getEventsForDate: I expect there is a call to the onSuccess or the onError block, and that call will receive what the block returns. The question is what getEventsForDate: does with that result. – gnasher729 Feb 01 '15 at 21:32

1 Answers1

3

So what you are looking for is more or less the following.

NSArray* (^eventsForDate)(NSDate *) = ^NSArray*(NSDate *date) {
    __block NSArray *result = nil;
    [[DataManager sharedInstance] getEventsForDate:date onSuccess:^(NSArray *events) {
        result = events; // Line A
    } onError:^(NSError *error) {
        result = @[]; // Line B
    }];
    return result; // Line C
};

The reason why you cannot use return at Line A and Line B (please refer to the comments in the code) is that you are returning the onSuccess: block function and the onError: block function instead of the eventsForDate:.

However, an important note: the above design will only work if your getEventsForDate: does not dispatch (or it always runs in the same thread of the eventsForData block.

Edit:

As mentioned above, this won't always work as expected if you have dispatch in your getEventsForDate:. Because you cannot guaranteed that Line C will get executed after Line A and Line B. It really comes down to how getEventsForDate: works. Here is another example to better illustrate the problem. Without a sleep(1) at the bottom of the code, you will probably get result as nil; with sleep(1), you will probably get @[@"A"].

- (void)getEventsForDate:(NSDate *)date
               onSuccess:(void(^)(NSArray *))sucessBlock
                 onError:(void(^)(NSError *))errorBlock
{
    dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(q, ^{
        sucessBlock(@[@"A"]);
    });
    // sleep(1);
}

So what you really have to do is to block Line C and wait till at least Line A or Line B get executed. Please refer to this question for more detail:

How do I wait for an asynchronously dispatched block to finish?

In short, you can write something like the following:

NSArray* (^eventsForDate)(NSDate *) = ^NSArray*(NSDate *date) {
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);

    __block NSArray *result = nil;
    [[DataManager sharedInstance] getEventsForDate:date onSuccess:^(NSArray *events) {
        result = events;
        dispatch_semaphore_signal(sema);
    } onError:^(NSError *error) {
        result = @[@"B"];
        dispatch_semaphore_signal(sema);
    }];

    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    return result;
};

Edit 2

However, even the above code may work for you. It doesn't sounds like a very good idea to go. That is because, if the getEventsForDate: get dispatched to the background thread, it means that it is very slow and would like to be executed in the background. And blocking your main thread with semephore or whatever will ruin everything. So a better approach is still to do whatever you want to do in the block and remove the return for good. Something like the following:

void (^eventsForDate)(NSDate *) = ^void(NSDate *date) {
    [[DataManager sharedInstance] getEventsForDate:date onSuccess:^(NSArray *events) {
        // Update UI and etc. 
    } onError:^(NSError *error) {
        // Update UI and etc. 
    }];
};
Community
  • 1
  • 1
Yuchen
  • 24,092
  • 18
  • 133
  • 193
  • Thanks Yuchen... how can make sure all of it runs on the same thread? Or is there a better design that I can try? – i_raqz Feb 01 '15 at 19:44
  • Well, this cannot be explain in one line. Give me a few minutes. I will update my answer. – Yuchen Feb 01 '15 at 19:48