做国际化的app, facebook登录是一定少不了的, 今天介绍一种facebook登录引起的崩溃.
先看堆栈
Thread 0 (crashed)
0 libobjc.A.dylib!objc_msgSend + 0x1c
Found by: given as instruction pointer in context
1 SafariServices!__75-[SFAuthenticationViewController dismissViewControllerAnimated:completion:]_block_invoke + 0x1c
Found by: previous frame's frame pointer
2 UIKit!-[UIPresentationController transitionDidFinish:] + 0x524
Found by: previous frame's frame pointer
3 UIKit!-[_UICurrentContextPresentationController transitionDidFinish:] + 0x28
Found by: previous frame's frame pointer
4 UIKit!__56-[UIPresentationController runTransitionForCurrentState]_block_invoke_2 + 0xb8
Found by: previous frame's frame pointer
5 UIKit!-[_UIViewControllerTransitionContext completeTransition:] + 0x70
Found by: previous frame's frame pointer
6 UIKit!-[UITransitionView notifyDidCompleteTransition:] + 0xf8
Found by: previous frame's frame pointer
7 UIKit!-[UITransitionView _didCompleteTransition:] + 0x464
Found by: previous frame's frame pointer
8 UIKit!-[UITransitionView _transitionDidStop:finished:] + 0x74
Found by: previous frame's frame pointer
9 UIKit!-[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 0x134
Found by: previous frame's frame pointer
10 UIKit!-[UIViewAnimationState animationDidStop:finished:] + 0x124
Found by: previous frame's frame pointer
11 UIKit!-[UIViewAnimationState animationDidStop:finished:] + 0x1c4
Found by: previous frame's frame pointer
12 QuartzCore!CA::Layer::run_animation_callbacks(void*) + 0x118
Found by: previous frame's frame pointer
13 libdispatch.dylib!_dispatch_client_callout + 0xc
Found by: previous frame's frame pointer
14 libdispatch.dylib!_dispatch_main_queue_callback_4CF$VARIANT$mp + 0x3f4
Found by: previous frame's frame pointer
15 CoreFoundation!__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 0x8
Found by: previous frame's frame pointer
16 CoreFoundation!__CFRunLoopRun + 0x7d8
Found by: previous frame's frame pointer
17 CoreFoundation!CFRunLoopRunSpecific + 0x1b0
Found by: previous frame's frame pointer
18 GraphicsServices!GSEventRunModal + 0x60
Found by: previous frame's frame pointer
19 UIKit!UIApplicationMain + 0xcc
Found by: previous frame's frame pointer
20 Hago!main [main.mm : 18 + 0x10]
Found by: previous frame's frame pointer
21 libdyld.dylib + 0x1568
Found by: previous frame's frame pointer
WTF, SFAuthenticationViewController
? 好像不是我们的代码哦, 这个实际上是系统的一个VC, 在我们点击facebook登录前, 会引导我们去到一个facebook授权页面, 这个页面就是SFAuthenticationViewController
类型的, 在测试中确实很难模拟出这种情况, 因为点击一次登录后就会弹出弹窗, 要我们确认, 这时候按道理是没机会再次点击facebook登录的, 但是如果用户的手机比较卡, 连续点了两次, 但是第二次没传递到按钮时候, 用户已经迫不及待的同意授权, 并唤出SFAuthenticationViewController
这时候第二次点击传递到了按钮, 触发了再次弹窗, 这时候再次点击确认就崩溃了.
分析: 我们程序在自动登录的时候, 如果发现facebook不能自动登录(token过期, 概率很小)也会触发弹出弹窗, 但这之前用户如果也点击了facebook登录, 并且点击弹窗的继续, 并且来到SFAuthenticationViewController
页面才触发自动登录失败的弹窗, 这时候再次点击弹窗就崩溃了.
代码重现崩溃
- (void)tp_login:(PKTpLoginCallback)callback timeout:(TimeOutHandler)timeout
{
[self fbNewLogin:callback timeout:timeout];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self fbNewLogin:callback timeout:timeout];
});
}
在3秒内, 下次弹窗弹出前, 触发弹窗, 并点击继续, 来到SFAuthenticationViewController
页面. 3秒到, 再次弹窗, 点击, 崩溃~
还有以下几种堆栈也是类似的问题
Thread 0 (crashed)
0 libobjc.A.dylib!objc_object::release() + 0x10
Found by: given as instruction pointer in context
1 SafariServices!__75-[SFAuthenticationViewController dismissViewControllerAnimated:completion:]_block_invoke + 0x1c
Found by: previous frame's frame pointer
2 SafariServices!__75-[SFAuthenticationViewController dismissViewControllerAnimated:completion:]_block_invoke + 0x1c
Found by: previous frame's frame pointer
3 UIKit!-[UIPresentationController transitionDidFinish:] + 0x524
Found by: previous frame's frame pointer
4 UIKit!-[_UICurrentContextPresentationController transitionDidFinish:] + 0x24
Found by: previous frame's frame pointer
5 UIKit!__56-[UIPresentationController runTransitionForCurrentState]_block_invoke.436 + 0xb8
Found by: previous frame's frame pointer
6 UIKit!-[_UIViewControllerTransitionContext completeTransition:] + 0x70
Found by: previous frame's frame pointer
7 UIKit!-[UITransitionView notifyDidCompleteTransition:] + 0xf8
Found by: previous frame's frame pointer
8 UIKit!-[UITransitionView _didCompleteTransition:] + 0x468
Found by: previous frame's frame pointer
9 UIKit!-[UITransitionView _transitionDidStop:finished:] + 0x74
Found by: previous frame's frame pointer
10 UIKit!-[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 0x134
Found by: previous frame's frame pointer
11 UIKit!-[UIViewAnimationState animationDidStop:finished:] + 0x124
Found by: previous frame's frame pointer
12 UIKit!-[UIViewAnimationState animationDidStop:finished:] + 0x1c4
Found by: previous frame's frame pointer
13 QuartzCore!CA::Layer::run_animation_callbacks(void*) + 0x118
Found by: previous frame's frame pointer
14 libdispatch.dylib!_dispatch_client_callout + 0xc
Found by: previous frame's frame pointer
15 libdispatch.dylib!_dispatch_main_queue_callback_4CF$VARIANT$mp + 0x3f0
Found by: previous frame's frame pointer
16 CoreFoundation!__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 0x8
Found by: previous frame's frame pointer
17 CoreFoundation!__CFRunLoopRun + 0x8dc
Found by: previous frame's frame pointer
18 CoreFoundation!CFRunLoopRunSpecific + 0x224
Found by: previous frame's frame pointer
19 GraphicsServices!GSEventRunModal + 0x60
Found by: previous frame's frame pointer
20 UIKit!UIApplicationMain + 0xe8
Found by: previous frame's frame pointer
21 Hago!main [main.mm : 18 + 0x10]
Found by: previous frame's frame pointer
22 libdyld.dylib!start + 0x0
Found by: previous frame's frame pointer
问题出现在这段代码
FBSDKApplicationDelegate.m
if (@available(iOS 11.0, *)) {
if ([sender isAuthenticationURL:url]) {
Class SFAuthenticationSessionClass = fbsdkdfl_SFAuthenticationSessionClass();
if (SFAuthenticationSessionClass != nil) {
WEAKIFYSELF
_authenticationSession = [[SFAuthenticationSessionClass alloc] initWithURL:url callbackURLScheme:[FBSDKInternalUtility appURLScheme] completionHandler:^ (NSURL *aURL, NSError *error) {
handler(error == nil, error);
STRONGIFYSELF
if (error == nil) {
[self application:[UIApplication sharedApplication] openURL:aURL sourceApplication:@"com.apple" annotation:nil];
}
self.authenticationSession = nil;
}];
[self.authenticationSession start];
return;
}
}
}
这里发现是iOS11系统先来一段装B的代码, 就是这段代码造成了后续的崩溃. 这里我的解决办法就是注释掉这段装B的代码, 直接走后面的逻辑. 当然, 还是要直面问题.
stackoverflow已经给出了解决办法.
- (void)tp_login:(PKTpLoginCallback)callback timeout:(TimeOutHandler)timeout
{
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
if (![topController isKindOfClass:NSClassFromString(@"SFSafariViewController")])
{
[self fbNewLogin:callback timeout:timeout];
}
}
- (void)fbNewLogin:(PKTpLoginCallback)callback
timeout:(TimeOutHandler)timeout
{
[_fbLoginMgr logOut];
_fbLoginMgr.loginBehavior = FBSDKLoginBehaviorBrowser;
[_fbLoginMgr logInWithReadPermissions:@[ @"public_profile", @"email", @"user_friends", @"user_birthday", @"user_gender" ] fromViewController:nil handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
MFLogInfo(LogTag, @"facebook login result.grantedPermissions = %@, error = %@", result.grantedPermissions, error);
if (error) {
safetyCallblock(callback, kPKLoginResultTpAuthFailed, error.description, nil);
MFLogError(LogTag, @"Process error");
} else if (result.isCancelled) {
safetyCallblock(callback, kPKLoginResultTpLoginCancel, nil, nil);
MFLogInfo(LogTag, @"Cancelled");
} else {
if (result.token.userID.length > 0) {
[[NSNotificationCenter defaultCenter] postNotificationName:kPKFbAuthSuccessNotification object:nil userInfo:@{kPKFbAuthInfoKey : result.token.userID}];
}
HGTpAuthRes *authInfo = [[HGTpAuthRes alloc] init];
authInfo.accessToken = result.token.tokenString;
authInfo.openId = result.token.userID;
authInfo.tpLoginType = kPKTpLoginTypeFacebook;
//https://developers.facebook.com/docs/graph-api/reference/user 接口文档
NSDictionary *parameters = @{ @"fields" : @"id,name,gender,birthday,picture.width(1080).height(1080)" };
[[[FBSDKGraphRequest alloc] initWithGraphPath:@"me" parameters:parameters] startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
if (!error && [result isKindOfClass:[NSDictionary class]]) {
MFLogInfo(LogTag, @"third parth get userinfo succ");
authInfo.tpUserInfo = [self parseUserInfo:result];
}
safetyCallblock(callback, kPKLoginResultOk, nil, authInfo);
}];
MFLogInfo(LogTag, @"third parth log in succ");
}
}];
}
这里在每次调用前都判断下当前present的vc是否是SFSafariViewController
的子类, 这样就没问题了, 等待上线看效果.