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.
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!
If you are dispatching an asynchronouos block, you can communicate with your UIViewController
subclass by calling back to it:
[self someSelectorWithCallbackData:stuffWhichYouWantToGiveBack];
This will call self
to get captured by the block, and so will work as expected. From the relevant method you can refresh the view / reload the tableview / dance a jig as required.
Depending on the context, you may need to __block
scope self
, eg
__block UIViewController *bsself = self;
But if you do the latter, be careful to avoid a retain loop (the build and analysis tools are fairly good at pointing this out).
Think you need to use a protol @class Webservice;
@protocol WebserviceDelegate
@optional
-(void)webservice:(Webservice *)webservice didFetchPosts:(NSArray *)posts;
-(void)webservice:(Webservice *)webservice didFetchComments:(NSArray *)comments forPostID:(NSString *)postID launchComments:(BOOL)launch;
-(void)webservice:(Webservice *)webservice didLoginWithUser:(User *)user;
-(void)webservice:(Webservice *)webservice didVoteWithSuccess:(BOOL)success forObject:(id)object direction:(BOOL)up;
@end
@interface Webservice : NSObject {
__weak id <WebserviceDelegate> delegate;
}
//Delegate
@property (weak) id <WebserviceDelegate> delegate;
-(void)getHomepage {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSURLResponse *response;
NSError *error;
// Create the URL Request
NSMutableURLRequest *request = [Webservice NewGetRequestForURL:[NSURL URLWithString:@"https://www.hnsearch.com/bigrss"]];
// Start the request
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//Handle response
//Callback to main thread
if (responseData) {
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSStringEncodingConversionAllowLossy];
if (responseString.length > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self parseIDsAndGrabPosts:responseString];
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:nil];
});
}
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:nil];
});
}
});
}
-(void)parseIDsAndGrabPosts:(NSString *)parseString {
// Parse String and grab IDs
NSMutableArray *items = [@[] mutableCopy];
NSArray *itemIDs = [parseString componentsSeparatedByString:@"<hnsearch_id>"];
for (int xx = 1; xx < itemIDs.count; xx++) {
NSString *idSubString = itemIDs[xx];
[items addObject:[idSubString substringWithRange:NSMakeRange(0, 13)]];
}
// Send IDs back to HNSearch for Posts
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSURLResponse *response;
NSError *error;
// Create Request String
NSString *requestString = @"http://api.thriftdb.com/api.hnsearch.com/items/_bulk/get_multi?ids=";
for (NSString *item in items) {
requestString = [requestString stringByAppendingString:[NSString stringWithFormat:@"%@,", item]];
}
// Create the URL Request
NSMutableURLRequest *request = [Webservice NewGetRequestForURL:[NSURL URLWithString:requestString]];
// Start the request
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//Handle response
//Callback to main thread
if (responseData) {
NSArray *responseArray = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:&error];
if (responseArray) {
NSMutableArray *postArray = [@[] mutableCopy];
for (NSDictionary *dict in responseArray) {
[postArray addObject:[Post postFromDictionary:dict]];
}
NSArray *orderedPostArray = [self orderPosts:postArray byItemIDs:items];
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:orderedPostArray];
// Update Karma for User
if ([HNSingleton sharedHNSingleton].User) {
[self reloadUserFromURLString:[NSString stringWithFormat:@"https://news.ycombinator.com/user?id=%@", [HNSingleton sharedHNSingleton].User.Username]];
}
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:nil];
});
}
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:nil];
});
}
});
}
来源:https://stackoverflow.com/questions/15134394/method-returning-value-from-asynchronous-block-with-facebooksdk