6

出于某种奇怪的原因,Facebook 决定创建实际上只是通知观察者的完成处理程序。忽略他们为什么这样做,我需要一些方法来请求打开一个 FBSession 并且只发生一次回调,因为回调中的对象不能被保留。

如果用户按下按钮并且具有“publish_actions”权限的 Facebook 会话未打开,则应显示活动指示器并防止在打开 Facebook 会话时与 UI 交互。

这是用于登录 Facebook 的方法:

- (void)loginToFacebookFromViewController:(UIViewController *)viewController completion:(void (^)(NSString *facebookAccountName))completion
{
    if([self isLoggedIntoFacebook] == NO)
    {
        [viewController startLoadingAnimation];

        [FBSession openActiveSessionWithPublishPermissions: @[ @"publish_actions" ] defaultAudience:FBSessionDefaultAudienceFriends allowLoginUI:YES completionHandler:^(FBSession *session, FBSessionState status, NSError *error)
            {
                if(error == nil)
                {
                    [FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id<FBGraphUser> user, NSError *error)
                        {
                            NSString *accountName = [user name];

                            if(error == nil)
                            {
                                NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
                                [standardUserDefaults setObject:accountName forKey:TSFacebookAccountName];
                                [standardUserDefaults synchronize];
                            }

                            [viewController stopLoadingAnimation];

                            if(completion != nil)
                                completion(accountName);
                        }];
                }
                else
                {
                    [viewController stopLoadingAnimation];

                    if(completion != nil)
                        completion(nil);
                }
            }];
    }
    else
    {
        if(completion != nil)
            completion([self facebookAccountName]);
    }
}

如果没有保留完成处理程序(再次出于毫无意义的原因,因为 NSNotifications 在会话状态更改时发布),那么此方法将完美运行。

因为该应用程序不围绕 Facebook 旋转,所以我对会话状态何时更改没有任何兴趣。如果用户尝试向 Facebook 分享内容并且活动会话未打开,则登录过程将被启动,如果会话打开,则共享过程将继续。否则,将向用户显示错误消息。

有人能解释一下 Facebook 是如何期望实现这种功能的吗,因为这似乎是一个很常见的用例,在有人提到“你为什么不使用默认的 Facebook 对话框”之前,这是因为需要向用户收集用于 Open Graph 帖子的特定信息。

4

2 回答 2

7

如果您不关心像我一样从 FBSessionStateHandler 接收状态更改,那么下面的代码应该允许您登录 Facebook 并为您提供未保留的完成块。

当发生任何状态更改时,传递到方法中的完成块将始终被使用,然后立即 nil'd ,这样如果发生另一个状态更改,它将被忽略。

typedef void(^FacebookLoginCompletionBlock)(id<FBGraphUser> user);

- (void)loginToFacebookFromWithCompletionBlock:(FacebookLoginCompletionBlock)completionBlock
{
    // If the user is not logged into Facebook attempt to open a session with "publish_actions" permissions.
    if([[FBSession activeSession] isOpen] == NO)
    {
        // Copy the completion block to a local variable so it can be nil'd out after it is used to prevent retain loops.
        __block FacebookLoginCompletionBlock copiedCompletionBlock = completionBlock;

        [FBSession openActiveSessionWithPublishPermissions:@[ @"publish_actions" ] defaultAudience:FBSessionDefaultAudienceFriends allowLoginUI:YES completionHandler:^(FBSession *session, FBSessionState status, NSError *error)
            {
                // Only attempt to run any of this code if there is a completion block to call back to. If completion block is nil than it has already been used and this is a state change that we do not care about.
                if(copiedCompletionBlock != nil)
                {
                    // Because this method is only concerned with the user logging into Facebook just worry about the open state occuring with no errors.
                    if(status == FBSessionStateOpen && error == nil)
                    {
                        // If the user successfully logged into Facebook download their basic profile information so the app can save the information to display to the user what account they are logged in under.
                        [FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id<FBGraphUser> user, NSError *error)
                            {
                                if(copiedCompletionBlock != nil)
                                    copiedCompletionBlock(user);

                                // nil out the copied completion block so it is not retained and called everytime the active FBSession's state changes.
                                copiedCompletionBlock = nil;
                            }];
                    }
                    // Because this method is only concerned with the user logging into Facebook if any other state is triggered call the completion block indicating that there was a failure.
                    else
                    {
                        if(copiedCompletionBlock != nil)
                            copiedCompletionBlock(nil);

                        // nil out the copied completion block so it is not retained and called everytime the active FBSession's state changes.
                        copiedCompletionBlock = nil;
                    }
                }

                // This block will exist the lifespan of the application because for some bizarre reason Facebook retains the completion handler for their open active session methods. Throw in some code that will display an error to the user if any session state changes occur that Facebook thinks the user should be aware of. Your code should be always checking if a active Facebook session exists before taking any action so not being aware of these changes should not be any issue. Worst case scenario you can listen for FBSessionDidSetActiveSessionNotification, FBSessionDidUnsetActiveSessionNotification, FBSessionDidBecomeOpenActiveSessionNotification or FBSessionDidBecomeClosedActiveSessionNotification notifications.
                if ([error fberrorShouldNotifyUser] == YES)
                {
                    NSString *alertTitle = @"Error logging into Facebook";
                    NSString *alertMessage = [error fberrorUserMessage];

                    if ([alertMessage length] == 0)
                        alertMessage = @"Please try again later.";

                    UIAlertView *alertView =  [[UIAlertView alloc] initWithTitle:alertTitle message:alertMessage delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];

                    [alertView show];
                }
            }];
    }
    // If the user is already logged into Facebook immediately call the completion block with the user object that should have been saved when the user previously logged in.
    else
    {
        if(completionBlock != nil)
            completionBlock([self facebookUser]);
    }
}
于 2013-05-24T19:44:38.337 回答
1

我实现类似行为的方式是将我的会话逻辑放在一个单例中。单例实现了一个完成处理程序,当会话发生变化时,FB 可以安全地调用该处理程序。然后,我的完成处理程序会发布自己的通知,以供其他控制器根据需要进行观察。

假设您有一个MySession单例,您在其中定义了一些关于 FB auth 的方法和通知。您的视图控制器逻辑将类似于:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // observe MySession notifications
    [MySession.sharedSession addObserver:self];
}

- (void)dealloc
{
    // stop observing MySession notifications
    [MySession.sharedSession removeObserver:self];
}

// this is a user action that requires FB auth
- (void)someActionRequiringFacebookAuth
{
    if (![MySession.sharedSession isLoggedIntoFacebook]) {
        self.postAuthAction = @selector(someActionRequiringFacebookAuth);
        [MySession.sharedSession loginToFacebook];
    }

    else {
        // perform action
    }
}

// notification posted by MySession before a FB auth attempt
- (void)mySessionWillAttemptFacebookAuth:(NSNotification *)notification
{
    [self startLoadingAnimation];
}

// notification posted by MySession if a FB auth attempt succeeds
- (void)mySessionDidAuthWithFacebook:(NSNotification *)notification
{
    [self stopLoadingAnimation];
    if (self.postAuthAction) {
        [self performSelector:self.postAuthAction];
        self.postAuthAction = nil;
    }
}

// notification posted by MySession if a FB auth attempt fails
- (void)mySessionFacebookAuthDidFail:(NSNotification *)notification
{
    [self stopLoadingAnimation];
    // display error if desired
    self.postAuthAction = nil;
}
于 2013-04-17T02:14:38.853 回答