Как правильно выполнить аутентификацию GameCenter?
Я видел в сообщениях о переполнении стека, которые показывают фрагменты обработки аутентификации GameCenter. Однако ни одно из этих решений не решает ни одной из проблем, которые решаются в реальных случаях использования. А именно, [GKLocalPlayer localPlayer].authenticateHandler - это просто обратный вызов состояния, и не более того. Он предоставляет контроллер представления, но существуют серьезные несоответствия в состояниях.authenticated и error.
Есть несколько вещей, которые я пытаюсь сделать: 1. Не открывать вход в игровой центр до тех пор, пока какая-либо функция не использует его. 2. Попробуйте выполнить автоматическую аутентификацию при запуске приложения. 3. Предоставьте пользователю некоторую информацию о том, почему функции GameCenter не работают. 4. Обеспечить механизм восстановления
А именно, если есть сообщение об ошибке, как я могу показать диалог входа?
Я получаю эту ошибку без viewController:
Случай 1:
Ошибка в GameCenterManager::authenticateLocalPlayer [Интернет-соединение отключено.]
Несмотря на сообщение об ошибке, устройство полностью подключено к сети, так как safari прекрасно загружает cnn.com.
Случай 2:
Кто-то закрывает экран входа в систему, потому что он не готов, и в этом случае.authenticated возвращается как истина, viewController остается равным нулю, но все вызовы игрового центра не будут выполнены. Почему [GKLocalPlayer localPlayer].authenticated имеет значение true, если это не так?
Случай 3:
Ошибка в GameCenterManager::authenticateLocalPlayer [Операция не может быть завершена. (NSURLErrorDomain ошибка -1009.)]
Это происходит, но приложение ничего не может сделать для пользователя. В таком случае каким должен быть обмен сообщениями? Переключить приложения в Game Center и войти в систему?
Дело 4:
Ошибка в GameCenterManager::authenticateLocalPlayer [Запрошенная операция была отменена или отключена пользователем.]
Это происходит, если пользователь отменяет viewController, который приложение было предложено представить Apple. Тем не менее, также нет восстановления или обнаружения этого состояния.
Дело 5:
Ошибка в GameCenterManager::createMatch [Запрошенная операция не может быть завершена, поскольку локальный игрок не прошел проверку подлинности.]
Это происходит, если пользователь вошел в систему, но по какой-либо причине выходит из GameCenter, а затем возвращается в приложение. Приложению будет сообщено, что пользователь все еще аутентифицирован, когда его явно нет, но я не могу позвонить, чтобы вызвать еще один логин.
По сути, если GameCenter не просто работает тихо, что мы должны делать как дизайнеры приложений? Просматривать оповещения и предлагать им войти в систему с помощью приложения игрового центра и перезапустить приложение?
Вот мой код аутентификации:
//******************************************************
// Authenticate
//******************************************************
-(void)authenticateLocalPlayer:(bool)showLogin
{
if( showLogin && self.loginScreen != nil )
{ [[WordlingsViewController instance] presentViewController:self.loginScreen animated:YES completion:nil]; }
if( [GKLocalPlayer localPlayer].isAuthenticated )
{
NSDLog(NSDLOG_GAME_CENTER,@"GameCenterManager::authenticateLocalPlayer LocalPlayer authenticated");
}
__weak GameCenterManager* weakSelf = self;
[GKLocalPlayer localPlayer].authenticateHandler = ^(UIViewController *viewController, NSError *error)
{
if (error != nil)
{
NSDLog(NSDLOG_GAME_CENTER,@"Error in GameCenterManager::authenticateLocalPlayer [%@]", [error localizedDescription]);
}
else
{
if (viewController != nil)
{
NSDLog(NSDLOG_GAME_CENTER,@"GameCenter: No authentication error, but we need to login");
weakSelf.loginScreen = viewController;
}
else
{
if ( [GKLocalPlayer localPlayer].authenticated )
{
NSDLog(NSDLOG_GAME_CENTER,@"GameCenter localPlayer authenticated");
weakSelf.gameCenterAvailable = YES;
weakSelf.localPlayer = [GKLocalPlayer localPlayer];
[weakSelf retrieveFriends];
[weakSelf loadPlayerPhoto:weakSelf.localPlayer];
for ( id<GameCenterDelegate> listener in weakSelf.listeners )
{ [listener onPlayerAuthenticated]; }
}
else
{
weakSelf.gameCenterAvailable = NO;
}
}
}
};
}
Эта функция вызывается дважды: один раз при запуске приложения, чтобы надеяться создать действительное состояние входа, и 2-й, если пользователь не прошел проверку подлинности и пытается использовать функцию приложения, для которой требуется игровой центр. В этом приложении создается пошаговый матч или просмотр друзей.
1 ответ
Вы сталкиваетесь со многими из тех же жалоб, что и у меня по поводу API Game Center. Я пытался достичь того же, что и ты. Версия TL;DR: Game Center просто не поддерживает ее. ><Но есть некоторые вещи, которые вы можете сделать, чтобы уменьшить боль.
Одна общая вещь, которая помогла мне: не забудьте проверить оба NSError
а также это .underlyingError
имущество. Я видел несколько случаев, когда NSError
слишком расплывчато, чтобы быть полезным, но основная ошибка имеет более конкретные детали.
Случай 1: Можете ли вы поделиться доменом ошибок и кодом ошибки как для NSError, так и для basicError?
Случай 2: у меня есть открытая ошибка с Apple в течение долгого времени. Есть несколько случаев, в том числе в режиме "В самолете", когда аутентификация не выполняется, но .authenticated
возвращает истину. Когда я написал баг об этом, Apple закрыла его, заявив, что это "специально", чтобы игроки могли продолжать играть в игру, используя любые ранее кэшированные данные. Итак, я добавил ошибку к нескольким сценариям, где кэшированные данные вызывают значительные проблемы. Моя ошибка была вновь открыта и с тех пор там сидела. Философия дизайна выглядит так: "ну, просто продолжай, и, возможно, все получится в конце". Но в итоге это не сработало, пользователь застревает, не может играть, и они обвиняют мою игру, а не Apple.
Единственное смягчение, которое я нашел, это то, что вы уже делаете: всегда всегда проверяйте NSError
сначала в обработчике аутентификации. Контроллер вида и .authenticated
абсолютно ненадежны, если была установлена ошибка.
Если есть ошибка, я передаю ее одному выделенному обработчику ошибок, который отображает предупреждения для пользователей и сообщает им, что им нужно сделать для восстановления.
Случай 3: я также получил -1009. Из того, что я могу различить, это происходит, когда у меня есть сетевое соединение, но Game Center никогда не отвечал. Это может произойти из-за любого сбоя между моим маршрутизатором, включая серверы Game Center, которые не отвечают. Я часто видел это при использовании GC Test Servers. Не так много сейчас, когда тестовые серверы были объединены в среду prod.
Случай 4: Вы на 100% правы. в игре нет восстановления. Если пользователь отменяет аутентификацию, это конец строки. Единственный способ восстановиться - это убить игру (не просто выйти и снова войти) и перезапустить ее. Тогда, и только тогда, вы можете представить другой контроллер вида входа в систему.
Есть некоторые вещи, которые вы можете сделать, чтобы смягчить это, хотя. Это напрямую нарушит вашу цель № 1 - отложить вход в систему до тех пор, пока это не потребуется, но я не нашел ничего лучшего:
- Отключите кнопку "начать игру" (или что-то еще, что есть в вашей игре), пока вы не подтвердите, что вход в систему прошел без ошибок И вы не можете успешно загрузить пример списка лидеров из GC. Это доказывает сквозную связь.
- Если пользователь отменяет регистрацию, ваш обработчик аутентификации получит
NSError
домена =GKErrorDomain
и код =GKErrorCanceled
, Когда я вижу это комбо, я предупреждаю пользователя о том, что он не может играть в сетевые игры до тех пор, пока он не войдет в систему и для входа в систему ему придется остановить и перезапустить игру. - Пользователи были сбиты с толку, почему кнопка "Пуск" была отключена, поэтому я тоже добавил туда предупреждение. Я показываю кнопку, которая выглядит отключенной, но действительно включена. И когда они пытаются щелкнуть по нему, я снова представляю предупреждение о том, что им нужно войти в игровой центр, чтобы играть в сетевую игру.
Это отстой, но, по крайней мере, пользователь не застрял.
Случай 5: Это один из примеров, которые я привел в своей ошибке, упомянутой в случае 2. Позволяя пользователю думать, что он вошел в систему, когда на самом деле это не так, он пытается сделать то, чего он действительно не может сделать, и в конце концов случится что-то плохое.
Лучшее смягчение, которое я нашел для этого, такое же, как и в случае 4: не позволяйте пользователю начать сеанс, пока вы не увидите, что обработчик аутентификации запускается без ошибок, и вы не можете успешно загрузить пример таблицы лидеров, чтобы доказать сетевое соединение.
Фактически, выполняя поиск по всем моим базам кода, я никогда не использую.authenticated для каких-либо решений.
Сказав все это, вот мой обработчик аутентификации. Я не скажу, что это красиво, но пока пользователи не застревают в неисправимых ситуациях. Я предполагаю, что это случай отчаянных времен (работа с дерьмовым API) требует отчаянных мер (грязные обходные пути).
[localPlayer setAuthenticateHandler:^(UIViewController *loginViewController, NSError *error)
{
//this handler is called once when you call setAuthenticated, and again when the user completes the login screen (if necessary)
VLOGS (LOWLOG, SYMBOL_FUNC_START, @"setAuthenticateHandler completion handler");
//did we get an error? Could be the result of either the initial call, or the result of the login attempt
if (error)
{
//Here's a fun fact... even if you're in airplane mode and can't communicate to the server,
//when this call back fires with an error code, localPlayer.authenticated is set to YES despite the total failure. ><
//error.code == -1009 -> authenticated = YES
//error.code == 2 -> authenticated = NO
//error.code == 3 -> authenticated = YES
if ([GKLocalPlayer localPlayer].authenticated == YES)
{
//Game center blatantly lies!
VLOGS(LOWLOG, SYMBOL_ERROR, @"error.code = %ld but localPlayer.authenticated = %d", (long)error.code, [GKLocalPlayer localPlayer].authenticated);
}
//show the user an appropriate alert
[self processError:error file:__FILE__ func:__func__ line:__LINE__];
//disable the start button, if it's not already disabled
[[NSNotificationCenter defaultCenter] postNotificationName:EVENT_ENABLEBUTTONS_NONETWORK object:self ];
return;
}
//if we received a loginViewContoller, then the user needs to log in.
if (loginViewController)
{
//the user isn't logged in, so show the login screen.
[appDelegate presentViewController:loginViewController animated:NO completion:^
{
VLOGS(LOWLOG, SYMBOL_FUNC_START, @"presentViewController completion handler");
//was the login successful?
if ([GKLocalPlayer localPlayer].authenticated)
{
//Possibly. Can't trust .authenticated alone. Let's validate that the player actually has some meaningful data in it, instead.
NSString *alias = [GKLocalPlayer localPlayer].alias;
NSString *name = [GKLocalPlayer localPlayer].displayName;
if (alias && name)
{
//Load our matches from the server. If this succeeds, it will enable the network game button
[gameKitHelper loadMatches];
}
}
}];
}
//if there was not loginViewController and no error, then the user is already logged in
else
{
//the user is already logged in, so load matches and enable the network game button
[gameKitHelper loadMatches];
}
}];