做國(guó)際化的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
? 好像不是我們的代碼哦, 這個(gè)實(shí)際上是系統(tǒng)的一個(gè)VC, 在我們點(diǎn)擊facebook登錄前, 會(huì)引導(dǎo)我們?nèi)サ揭粋€(gè)facebook授權(quán)頁面, 這個(gè)頁面就是SFAuthenticationViewController
類型的, 在測(cè)試中確實(shí)很難模擬出這種情況, 因?yàn)辄c(diǎn)擊一次登錄后就會(huì)彈出彈窗, 要我們確認(rèn), 這時(shí)候按道理是沒機(jī)會(huì)再次點(diǎn)擊facebook登錄的, 但是如果用戶的手機(jī)比較卡, 連續(xù)點(diǎn)了兩次, 但是第二次沒傳遞到按鈕時(shí)候, 用戶已經(jīng)迫不及待的同意授權(quán), 并喚出SFAuthenticationViewController
這時(shí)候第二次點(diǎn)擊傳遞到了按鈕, 觸發(fā)了再次彈窗, 這時(shí)候再次點(diǎn)擊確認(rèn)就崩潰了.
分析: 我們程序在自動(dòng)登錄的時(shí)候, 如果發(fā)現(xiàn)facebook不能自動(dòng)登錄(token過期, 概率很小)也會(huì)觸發(fā)彈出彈窗, 但這之前用戶如果也點(diǎn)擊了facebook登錄, 并且點(diǎn)擊彈窗的繼續(xù), 并且來到SFAuthenticationViewController
頁面才觸發(fā)自動(dòng)登錄失敗的彈窗, 這時(shí)候再次點(diǎn)擊彈窗就崩潰了.
代碼重現(xiàn)崩潰
- (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秒內(nèi), 下次彈窗彈出前, 觸發(fā)彈窗, 并點(diǎn)擊繼續(xù), 來到SFAuthenticationViewController
頁面. 3秒到, 再次彈窗, 點(diǎn)擊, 崩潰~
還有以下幾種堆棧也是類似的問題
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
問題出現(xiàn)在這段代碼
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;
}
}
}
這里發(fā)現(xiàn)是iOS11系統(tǒng)先來一段裝B的代碼, 就是這段代碼造成了后續(xù)的崩潰. 這里我的解決辦法就是注釋掉這段裝B的代碼, 直接走后面的邏輯. 當(dāng)然, 還是要直面問題.
stackoverflow已經(jīng)給出了解決辦法.
- (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");
}
}];
}
這里在每次調(diào)用前都判斷下當(dāng)前present的vc是否是SFSafariViewController
的子類, 這樣就沒問題了, 等待上線看效果.