Bogdan Somlea
Bogdan Somlea

Reputation: 624

Block inside of method doesn't return

I have to folowing method.

I want this method to return 1 if the user liked the page, or 0 if the user didn't.But it seems like the block is called before the function.

Is there a way to solve the problem?

Thank you!.

-(NSInteger) userWithId:(NSString *)fb_id likesPageWithID:(NSString *)page_id {

    __block NSInteger returnValue;
    NSString *fbGraphPath = [NSString stringWithFormat:@"/%@/likes/%@",fb_id,page_id];
    NSLog(@"%@",fbGraphPath);
    [FBRequestConnection startWithGraphPath:fbGraphPath parameters:nil HTTPMethod:@"GET" completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
        NSLog(@"%@",result);
        if(error) {
            NSLog(@"%@",error);
        } else {
            if([result count] == 0) {
                NSLog(@"%@",[result objectForKey:@"data"]);
                returnValue = 0;

            } else {
                    NSLog(@"%@",[result objectForKey:@"data"]);

                returnValue = 1;

                }
            }
    }];
    return returnValue;
}

Upvotes: 2

Views: 173

Answers (3)

rtiago42
rtiago42

Reputation: 572

I can think of three ways to do this:

Notification Center

Like Apurv answer suggests, you can use notification centre to broadcast the result:

- (void)userWithId:(NSString *)fb_id likesPageWithID:(NSString *)page_id {

    __block NSInteger returnValue;
    NSString *fbGraphPath = [NSString stringWithFormat:@"/%@/likes/%@",fb_id,page_id];
    NSLog(@"%@",fbGraphPath);
    [FBRequestConnection startWithGraphPath:fbGraphPath parameters:nil HTTPMethod:@"GET" completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
        NSLog(@"%@",result);
        if(error) {
            NSLog(@"%@",error);
        } else {
            if([result count] == 0) {
                NSLog(@"%@",[result objectForKey:@"data"]);
                returnValue = 0;

            } else {
                    NSLog(@"%@",[result objectForKey:@"data"]);

                returnValue = 1;

                }
            }
        [[NSNotificationCenter defaultCenter] postNotificationName:@"mynotif" object:nil userInfo:@{@"result":@(returnValue)}];
    }];
}

Personally, I wouldn't do this unless I needed to broadcast the value. Using notifications adds some unnecessary overhead.

Condition

This is the simplest to implement, and it doesn't change who you use this method, but is a bit overkill.

- (NSInteger)userWithId:(NSString *)fb_id likesPageWithID:(NSString *)page_id {

    __block NSInteger returnValue;
    NSCondition *condition = [NSCondition new];
    [condition lock];
    NSString *fbGraphPath = [NSString stringWithFormat:@"/%@/likes/%@",fb_id,page_id];
    NSLog(@"%@",fbGraphPath);
    [FBRequestConnection startWithGraphPath:fbGraphPath parameters:nil HTTPMethod:@"GET" completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
        NSLog(@"%@",result);
        if(error) {
            NSLog(@"%@",error);
        } else {
            if([result count] == 0) {
                NSLog(@"%@",[result objectForKey:@"data"]);
                returnValue = 0;

            } else {
                    NSLog(@"%@",[result objectForKey:@"data"]);

                returnValue = 1;

                }
            }
        [condition lock];
        [condition signal];
        [condition unlock];
    }];
    [condition wait];
    [condition unlock];
}

Also, this code will block the running thread. So the thread that calls this method can't be the same that runs the FB's completion handler.

Blocks

This is, in my opinion, the best.

- (void)userWithId:(NSString *)fb_id likesPageWithID:(NSString *)page_id withCompletionHandler:(void (^)(NSInteger))handler {

    __block NSInteger returnValue;
    NSString *fbGraphPath = [NSString stringWithFormat:@"/%@/likes/%@",fb_id,page_id];
    NSLog(@"%@",fbGraphPath);
    [FBRequestConnection startWithGraphPath:fbGraphPath parameters:nil HTTPMethod:@"GET" completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
        NSLog(@"%@",result);
        if(error) {
            NSLog(@"%@",error);
        } else {
            if([result count] == 0) {
                NSLog(@"%@",[result objectForKey:@"data"]);
                returnValue = 0;

            } else {
                    NSLog(@"%@",[result objectForKey:@"data"]);

                returnValue = 1;

                }
            }
        handler(returnValue);
    }];
}

It's easy to use it to:

[var userWithId:fb_id likesPageWithID:page_id withCompletionHandler:^(NSInteger result) {
    // use result for whatever you need
}];

Hope it helps.

Upvotes: 3

Jasper Blues
Jasper Blues

Reputation: 28766

Almost always the answer given by @Apurv will be correct, you'll do either of the following:

  • Pass in a block that contains parameter(s) for the data you'd like to retrieve (best for one to one)
  • Fire a notification (best for publish subscribe).

Though to answer your question directly, if you have an external library that provides asynchronous callbacks and you really would like to invoke it synchronously you can do the following:

Option 1: Use a semaphore

A semaphore halts execution until a signaled to proceed.

-(NSInteger) userWithId:(NSString *)fb_id likesPageWithID:(NSString *)page_id {

    dispatch_semaphore_t querySemaphore = dispatch_semaphore_create(0);

    __block NSInteger returnValue;        
    [FBRequestConnection startWithGraphPath:fbGraphPath parameters:nil HTTPMethod:@"GET" completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {

        returnValue = xyz; //calculate your return value
        dispatch_semaphore_signal(querySemaphore);


    }];
    dispatch_semaphore_wait(querySemaphore, DISPATCH_TIME_FOREVER);
    return returnValue;
}

Option 2: Use Runloop (hacky)

If your block is calling back on the same thread/queue that the dispatch was done from, a deadlock will occur. One way to avoid this is to use the run loop.

for (int i = 0; i < 5; i++) //time-out
{
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    if (returnValue)
    {
        return returnValue;
    }
}
return nil;

Upvotes: 1

Apurv
Apurv

Reputation: 17186

Blocks work asynchronously. So, when you call startWithGraphPath, the execution goes to the next line directly. Your completion block will be called once the caller method finishes its execution. So, in general you should not return the value. But at the end of block, you should broadcast the notification with proper value encapsulated in it.

Upvotes: 1

Related Questions