我正在测试基于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];
}
}];
}
}];以下是防火规则:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}问题是,大约有10%的时间,聊天消息的UITableView是空的,我在日志中没有看到任何聊天消息条目。我试着使用observeEventType的命令,在loginToFacebookAppWithId调用之前和之后放置它。
我在想,在我打电话给observeEventType之前,是否存在消息到达的竞赛条件。我检查了observeEventType的返回值,即使没有消息到达,也得到了1的FirebaseHandle。我还将firechat附带的防火墙框架升级到https://cdn.firebase.com/ObjC/Firebase.framework-LATEST.zip,但仍然失败。
我认为连接可能中断了,但是我能够在通过身份验证后用childByAutoId发布消息,并看到它们出现在firebase服务器上。我只是从来没有收到过任何信息。
我不知道它是否试图在我通过身份验证之前的短时间内向我发送消息,但由于我没有读取权限,所以失败了。有什么办法把事件的观察推迟到我加入之后吗?
我已经试过了我能想到的一切,但我无法使它可靠地工作。
如果手动输入凭据,我似乎每次都可以登录。目前,我正在检查先前成功登录的以下内容:
[FBSession openActiveSessionWithAllowLoginUI:false]以确定我是否在上一次启动应用程序时成功登录。如果失败,我将转到FirebaseSimpleLogin的视图控制器。但是,如果它有效,我将在当前视图控制器中调用FirebaseSimpleLogin,并等待它在后台成功运行。
我在模拟器中运行,所以我尝试在以下位置删除preferences plist:
~/Library/Application Support/iPhone Simulator/7.0.3/Applications/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Library/Preferences/com.xxxxxxxxxx.plist重新启动,这迫使我重新确认身份。然后,我试着输入我的凭证,登录25次,没有问题。
因此,我认为问题要么与在使用FirebaseSimpleLogin之前尝试使用Facebook登录有关,要么与使用上一次启动时的凭据登录有关(而不打开登录对话框)。我还在努力缩小罪魁祸首。
我只想补充一句,经过进一步的测试,我发现调用:
[FBSession openActiveSessionWithAllowLoginUI:false]对FirebaseSimpleLogin无影响。如果我完全跳过这个调用,在那里简单地替换为true或false,我可以复制这个问题。这个问题被证明是种族问题,见下面的答案。
发布于 2014-02-28 18:51:37
我终于明白了发生了什么,这是由于我对UIViewController消息回调和CFRunLoop的错误假设。
我问题中的代码示例是从我真正的代码中提炼出来的,目的是删除无关的调用,但事实证明,我删除的部分实际上是罪魁祸首。通过使用run循环,我编写了一个函数来登录并等待成功或失败(而不是稍后在块中接收响应):
-(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消息,但随后我启动了一个run循环。run循环允许在后台进行处理,即使主线程在CFRunLoopRun()上暂停,直到完成块调用CFRunLoopStop()。
我没有意识到的是,运行循环继续在后台处理应用程序的消息。因此,当我认为程序流在viewDidLoad中停止时,它实际上一直在继续,并调用了viewWillAppear,这就是我调用observeEventType的地方(因为我假设程序到达时,身份验证已经完成)。
这创建了一个种族条件,该程序在Facebook和Firebase进行身份验证期间附加了observeEventType回调。90%的时间,身份验证在observeEventType被调用之前已经完成,但有10%的时间存在滞后或其他网络延迟,而observeEventType被过早地调用。
我通过将FirebaseSimpleLogin代码移动到情节提要中的自己的视图控制器,并使用完成块来启动下一个安装了observeEventType回调的视图控制器的segue来解决这个问题。
总之,是这样的:解决方案是调用FirebaseSimpleLogin的身份验证,然后在它完成并完成块之后,调用observeEventType。否则,Firebase的规则将拒绝您的请求,以查看只有经过身份验证的用户才能看到的数据(这是正确的)。
下面是最后的代码,未经测试,但该方法有效:
// 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];
}];
}https://stackoverflow.com/questions/22073318
复制相似问题