Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

ios - Method returning value from asynchronous block with FacebookSDK

What I am trying to do is a Facebook wrapper for the Facebook iOS SDK. Basically the idea is that my ViewController should do nothing but showing ex. my friends that will be acquired with a simple call like

self.friends = [FacebookWrapper myFriends];
[self.tableView reloadData];

My wrapper myFriends method should look like this

+ (NSArray *)myFriends
{
   __block NSArray *friends = nil;
   [FBSession openActiveSessionWithReadPermissions:nil allowLoginUI:YES completionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
    if(FB_ISSESSIONOPENWITHSTATE(status)) {
    [FBRequestConnection startForMyFriendsWithCompletionHandler:^(FBRequestConnection *connection, id data, NSError *error) {
       CFRunLoopStop(CFRunLoopGetCurrent());
        if(error) {
            return;
        }
        NSArray *friendsData = (NSArray *)[data data];
        NSMutableArray *fbFriends = [NSMutableArray array];
        for(id friendData in friendsData) {
            Friend *friend = [Friend friendWithDictionary:friendData];
            fbFriends addObject:friend];
        }
        friends = [NSArray arrayWithArray:fbFriends];
    }];
    CFRunLoopRun();
    }
  }];
  return friends;
}

The issue is that the openActiveSessionWithReadPermissions and startForMyFriendsWithCompletionHandler are asynchronous blocks so the method returns before the blocks complete their task.

Any help would be much appreciated.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I created a similar wrapper in the past and my approach was passing a "completion block" when calling my wrapper method; this completion block is then triggered once all the asynchronous calls are done running, and it receives whatever data your method would return in a synchronous scenario (in your case, the array of friends).

To illustrate - you could have your "myFriends" method redefined as:

+ (void)myFriendsWithCompletionBlock:(void (^)(NSArray *friends))completionBlock;

Then in the implementation, right after the friends = [NSArray arrayWithArray:fbFriends]; line, you would add this:

if (completionBlock != nil) {
    completionBlock(friends);
}

... and remove the return statement at the end.

Finally, on your view controller (or any object using the method, you would do something like this:

[FacebookWrapper myFriendsWithCompletionBlock:^(NSArray *friends){
    // do what you need to do with the friends array
}];

Of course, this is still asynchronous - but there's no way around since that's how the Facebook SDK was build (and, to be fair, this is probably the best way to do it - waiting for requests to finish synchronous would be terrible!)

Edit: I noticed you're also returning from the wrapper method in case it fails; in that situation, instead of returning you would do something like this:

if (completionBlock != nil) {
    completionBlock(nil);
}

That would cause the friends array to be nil when your completion block is called - you can then treat that error there however seems appropriate to you.

Hope this helped!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...