Any way to have completionHandler style syntax using [facebook authorize] on iOS 5?

浪子不回头ぞ 提交于 2019-12-23 02:45:32

问题


The code below is trying to lazy login to Facebook right before posting a photo, but has an asynchronous problem. In the logs, the after isSessionValid block will appear before the fbDidLogin and then a facebookErrDomain error 10000 will happen ("OAuthException", "active access token must be used", etc).

MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
if (![appDelegate.facebook isSessionValid]) {
    [appDelegate.facebook authorize:[NSArray arrayWithObjects:@"publish_stream", @"user_photos", nil]];
}

NSLog(@"after isSessionValid block");

NSData *imageData = UIImageJPEGRepresentation(image, 1);
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                               FACEBOOK_APP_ID, @"app_id",
                               imageData, @"source",
                               message,  @"message",
                               nil];

[appDelegate.facebook requestWithGraphPath:@"me/photos" andParams:params andHttpMethod:@"POST" andDelegate:self];

Here is the fbDidLogin in MyAppDelegate

- (void)fbDidLogin {
    NSLog(@"fbDidLogin");

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:[facebook accessToken] forKey:@"FBAccessTokenKey"];
    [defaults setObject:[facebook expirationDate] forKey:@"FBExpirationDateKey"];
    [defaults synchronize];
}

I realize that the facebook requestWithGraphPath is running before the fbDidLogin on the FBSessionDelegate but not sure the best way to take the code below the after isSessionValid block log statement and have it run inside fbDidLogin?

Question

I would love to have a completionHandler style API like below. Is there an easy way do that? Alternatively, is there good way to add a callback or block to MyAppDelegate that would be called once from fbDidLogin and then removed?

[appDelegate.facebook authorize:array completionHandler:^(BOOL success) {
    // other setup stuff from first example
    [appDelegate.facebook requestWithGraphPath:@"me/photos" andParams:params andHttpMethod:@"POST" andDelegate:self];
}];

Update

An answer to How to react to asynchronous events (login)? might be what I am looking for.


回答1:


One possibility is to use a GCD semaphore and put requestWithGraph: onto a background queue.

Add a property to MyAppDelegate to hold the semaphore; create it with dispatch_semaphore_create, passing 1 because you're dealing with two threads -- you only want one to be able to work at a time:

@property (assign) dispatch_semaphore_t authSemaphore;

authSemaphore = dispatch_semaphore_create(1);

Then decrement the semaphore right before you authorize:

if (![appDelegate.facebook isSessionValid]) {
    dispatch_semaphore_wait([appDelegate authSemaphore], DISPATCH_TIME_FOREVER);
    [appDelegate.facebook authorize:[NSArray arrayWithObjects:@"publish_stream", @"user_photos", nil]];
}

And signal it when the authorization succeeds:

- (void)fbDidLogin {
    //...
    [defaults synchronize];
    dispatch_semaphore_signal([self authSemaphore]);
}

Now you can wait on the semaphore right before you try to post the image. If authorization is in progress, the wait call will block, and the following code will not run until the semaphore is signaled. If the semaphore is available, however, the code will proceed normally.

You have to put this onto a background queue to avoid blocking the main thread, but you won't need to create a new one; one of the global concurrent queues should work fine:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_wait([appDelegate authSemaphore], DISPATCH_TIME_FOREVER);
    [appDelegate.facebook requestWithGraphPath:@"me/photos" andParams:params andHttpMethod:@"POST" andDelegate:self];
    dispatch_semaphore_signal([appDelegate authSemaphore]);
});



回答2:


Here is what I put in MyAppDelegate, which saves off a completionHandler, and either calls with YES or NO depending on login, then sets completionHandler to nil. I'm sure I should put a @try/@catch in there somewhere to ensure completionHandler gets set to nil.

@interface MyAppDelegate : UIResponder <UIApplicationDelegate, FBSessionDelegate>
{
    void (^_completionHandler)(BOOL success);
}

- (void)facebookAuthorizeWithCompletionHandler:(void (^)(BOOL success))completionHandler {
    if (![facebook isSessionValid]) {
        _completionHandler = completionHandler;
        [facebook authorize:[NSArray arrayWithObjects:@"publish_stream", @"user_photos", nil]];
    } else {
        completionHandler(YES);
    }
}

- (void)fbDidLogin {
    NSLog(@"fbDidLogin");
    // removed code that saves accessToken/expirationDate to NSUserDefaults

    if (_completionHandler) {
        _completionHandler(YES);
        _completionHandler = nil;
    }
}

- (void)fbDidNotLogin:(BOOL)cancelled {
    NSLog(@"fbDidNotLogin");

    if (_completionHandler) {
        _completionHandler(NO);
        _completionHandler = nil;
    }
}

I called into it using code like this, which seems to work (but I want to reread about Blocks and Variables to make sure I understand the memory management issues).

[appDelegate facebookAuthorizeWithCompletionHandler:^(BOOL success) {

    NSLog(@"in completionHandler, success=%d", success);

    if (success) {
        // other setup stuff from first example
        [appDelegate.facebook requestWithGraphPath:@"me/photos" andParams:params andHttpMethod:@"POST" andDelegate:self];
    }
}];



回答3:


You can write an extension to the Facebook object, use a secondary object (instance of another class) that registers itself as the delegate and stores the handler. You can use associative references to save that object into the Facebook instance. So, when the delegate methods get executed, that secondary object could simply execute the handler block.

In this project, there are a lot of examples of this (NSURLConnection-BKAdditions.m for example) Here you have another link that might help you with the associative references.



来源:https://stackoverflow.com/questions/9561421/any-way-to-have-completionhandler-style-syntax-using-facebook-authorize-on-ios

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!