首页 文章

Firebase使用Facebook后FirebaseSimpleLogin没有触发firebase observeEventType?

提问于
浏览
3

我正在测试基于firechat-ios示例的代码 . 我添加了FirebaseSimpleLogin调用loginToFacebookAppWithId并设置它,以便一个视图控制器执行登录,然后转换到保存聊天逻辑的不同视图控制器:

self.firebase = [[Firebase alloc] initWithUrl:@"https://xxxxx.firebaseio.com/"];

[self observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
    NSLog(@"%@", snapshot.value);

    // Add the chat message to the array.
    [self.chat addObject:snapshot.value];
    // Reload the table view so the new message will show up.
    [self.tableView reloadData];

    [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}];

FirebaseSimpleLogin *authClient = [[FirebaseSimpleLogin alloc] initWithRef:self.firebase];

[authClient loginToFacebookAppWithId:kFacebookAppID permissions:@[@"email"]
            audience:ACFacebookAudienceOnlyMe
            withCompletionBlock:^(NSError *error, FAUser *user) {

    if (error != nil) {
        // There was an error logging in
        NSLog(@"facebook error");
    } else {
        // We have a logged in facebook user
        NSLog(@"facebook logged in");

        [authClient checkAuthStatusWithBlock:^(NSError* error, FAUser* user) {
            if (error != nil) {
    // Oh no! There was an error performing the check
    NSLog(@"auth error");
            } else if (user == nil) {
    // No user is logged in
    NSLog(@"auth not logged in");
            } else {
    // There is a logged in user
    NSLog(@"auth logged in");

    // segue to the chat view controller
    [self performSegueWithIdentifier:@"segueToViewController" sender:self];
            }
        }];
    }
}];

以下是firebase规则:

{
    "rules": {
        ".read": "auth != null",
        ".write": "auth != null"
    }
}

问题是,大约10%的时间,聊天消息的UITableView是空白的,我在日志中看不到任何聊天消息条目 . 我已尝试使用observeEventType的顺序,在loginToFacebookAppWithId调用之前和之后放置它 .

我想知道在调用observeEventType之前是否存在可能消息到达的竞争条件 . 我已经检查了observeEventType的返回值,即使没有消息到达,我的FirebaseHandle也是1 . 我还将firechat ios附带的firebase框架升级到了https://cdn.firebase.com/ObjC/Firebase.framework-LATEST.zip,但它仍然失败了 .

我认为可能连接丢失了,但我可以在我进行身份验证后发布带有childByAutoId的消息,并看到它们出现在firebase服务器上 . 我从来没有收到任何消息 .

我想知道它是否正在尝试在我通过身份验证之前的短暂时间内向我发送消息,并且因为我没有读取权限而失败 . 有没有办法将事件观察延迟到我进入之后?

我已经尝试了我能想到的一切,但我无法让它可靠地工作 .

---------- UPDATE ----------

如果我手动输入凭据,我似乎每次都能登录 . 我目前正在检查以前的成功登录:

[FBSession openActiveSessionWithAllowLoginUI:false]

确定我是否在上次启动应用程序时成功登录 . 如果失败,我会转到FirebaseSimpleLogin的视图控制器 . 但如果它有效,我会在当前视图控制器中调用FirebaseSimpleLogin,并等到它在后台成功 .

我正在模拟器中运行,所以我尝试删除首选项plist:

~/Library/Application Support/iPhone Simulator/7.0.3/Applications/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Library/Preferences/com.xxxxxxxxxx.plist

和重新启动,这迫使我重新认证 . 然后我尝试输入我的凭据并登录25次没有问题 .

所以我认为这个问题要么在我使用FirebaseSimpleLogin之前尝试使用Facebook登录,要么使用之前启动时的凭据登录(不需要启动登录对话框) . 我还在努力缩小罪魁祸首 .

---------- UPDATE 2 ----------

我只想添加一个注释,经过进一步测试,我发现调用:

[FBSession openActiveSessionWithAllowLoginUI:false]

对FirebaseSimpleLogin没有影响 . 如果我完全跳过那个电话并简单地在那里替换真或假,我可以重现这个问题 . 问题原来是竞争条件,请参阅下面的答案 .

1 回答

  • 4

    我终于弄清楚发生了什么,这是因为我对UIViewController消息回调和CFRunLoop的错误假设 .

    我的问题中的代码示例是从我的真实代码中提取出来的,以删除多余的调用,但事实证明我删除的部分实际上是罪魁祸首 . 我编写了一个函数来登录并通过使用运行循环等到现场成功或失败(而不是稍后在块中接收响应):

    -(bool)loginUsingFacebookReturningError:(NSError**)error andUser:(FAUser**)user
    {
        __block NSError *errorTemp;
        __block FAUser  *userTemp;
    
        [self loginUsingFacebookWithCompletionBlock:^(NSError *error, FAUser *user) {
            errorTemp = error;
            userTemp = user;
    
            CFRunLoopStop(CFRunLoopGetCurrent());
        }];
    
        CFRunLoopRun(); // needs a timeout or way for the user to cancel but I haven't implemented it yet
    
        if(error) *error = errorTemp;
        if(user) *user = userTemp;
    
        return !errorTemp && userTemp;
    }
    
    -(void)loginUsingFacebookWithCompletionBlock:(void (^)(NSError* error, FAUser* user))block
    {
        FirebaseSimpleLogin *authClient = [[FirebaseSimpleLogin alloc] initWithRef:self.firebase];
    
        [authClient loginToFacebookAppWithId:kFacebookAppID permissions:@[@"email"]
            audience:ACFacebookAudienceOnlyMe
             withCompletionBlock:^(NSError *error, FAUser *user) {
    
                 if (error != nil) {
                     // There was an error logging in
                     NSLog(@"facebook error");
    
                     block(error, nil);
                 } else {
                     // We have a logged in facebook user
                     NSLog(@"facebook logged in");
    
                     [authClient checkAuthStatusWithBlock:block];
                 }
             }];
    }
    

    这被称为:

    NSError *error;
    FAUser  *user;
    bool    success = [self loginUsingFacebookReturningError:&error andUser:&user];
    

    loginUsingFacebookReturningError工作的方式是,它调用loginUsingFacebookWithCompletionBlock,它会像往常一样触发loginToFacebookAppWithId和checkAuthStatusWithBlock消息,但后来我开始运行循环 . 运行循环允许处理在后台进行,即使主线程在CFRunLoopRun()上暂停,直到完成块调用CFRunLoopStop() .

    What I hadn't realized is that run loops continue to process the application's messages in the background. 所以当我认为程序流已在viewDidLoad中停止时,它实际上已经继续并调用了viewWillAppear,这是我调用observeEventType的地方(因为我假设在程序到达时验证将完成) .

    这创建了一个race condition,其中程序在Facebook和Firebase进行身份验证期间附加了observeEventType回调 . 90%的时间,身份验证在调用observeEventType之前完成,但有10%的时间存在延迟或其他网络延迟,并且提前调用observeEventType .

    我通过将FirebaseSimpleLogin代码移动到故事板中自己的视图控制器来修复问题,并使用完成块将segue启动到安装observeEventType回调的下一个视图控制器 .

    So to summarize: the solution is to call FirebaseSimpleLogin's authentication, and then AFTER it has finished and the completion block is done, call observeEventType. Otherwise Firebase's rules will deny your request to see data that's only visible to authenticated users (which is correct).

    这是最终的代码,未经测试但是方法作品:

    // only global for illustration purposes, should really go in a singleton or AppDelegate, or be passed through the segue to the next view controller
    Firebase    *gFirebase;
    
    // LoginViewController (root view controller in storyboard)
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        gFirebase = [[Firebase alloc] initWithUrl:@"https://xxxxx.firebaseio.com/"];
    
        FirebaseSimpleLogin *authClient = [[FirebaseSimpleLogin alloc] initWithRef:gFirebase];
    
        [authClient loginToFacebookAppWithId:kFacebookAppID permissions:@[@"email"]
                    audience:ACFacebookAudienceOnlyMe
                    withCompletionBlock:^(NSError *error, FAUser *user) {
    
            if (error != nil) {
                // There was an error logging in
                NSLog(@"facebook error");
            } else {
                // We have a logged in facebook user
                NSLog(@"facebook logged in");
    
                [authClient checkAuthStatusWithBlock:^(NSError* error, FAUser* user) {
                    if (error != nil) {
                        // Oh no! There was an error performing the check
                        NSLog(@"auth error");
                    } else if (user == nil) {
                        // No user is logged in
                        NSLog(@"auth not logged in");
                    } else {
                        // There is a logged in user
                        NSLog(@"auth logged in");
    
                        // segue to the chat view controller
                        [self performSegueWithIdentifier:@"segueToViewController" sender:self];
                    }
                }];
            }
        }];
    }
    
    // ViewController (destination of segueToViewController)
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        [gFirebase observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
            NSLog(@"%@", snapshot.value);
    
            // Add the chat message to the array.
            [self.chat addObject:snapshot.value];
            // Reload the table view so the new message will show up.
            [self.tableView reloadData];
    
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
        }];
    }
    

相关问题