Запросы таблицы лидеров, вложенные блоки и циклы сохранения

Я разработал класс отображения таблицы лидеров для своей игры для iPhone. Класс имеет следующий метод экземпляра.

-(void)displayScoresWithRequest:(CBLeaderboard*)request completionHandler:(void(^)())completionHandler
{
    if (request_ != nil)
        return;

    request_ = [[CBLeaderboard alloc] init];
    [request_ setCategory:[request category]];
    [request_ setPlayerScope:[request playerScope]];
    [request_ setTimeScope:[request timeScope]];
    [request_ setRange:[request range]];

    __block CBLeaderboardDisplay* blockSelf = self;
    [request_ loadScoresWithCompletionHandler:^(NSArray* scores, NSError* error)
    {
        blockSelf->request_ = nil;

        NSUInteger scoresCount = [scores count];
        if (scoresCount == 0 && error != nil)
            return;

        NSMutableArray* playerIDs = [NSMutableArray array];
        for (GKScore* score in scores)
            [playerIDs addObject:[score playerID]];

        [GKPlayer loadPlayersForIdentifiers:playerIDs withCompletionHandler:^(NSArray* players, NSError* error)
        {
            if (scoresCount > [players count] && error != nil)
                return;

            [blockSelf displayScores:scores players:players];

            completionHandler();
        }];
    }];


    [request_ release];
}

Как видите, метод копирует запрос списка лидеров, выполняет его и вызывает предоставленный обработчик завершения. Слой в моей игре вызывает этот метод следующим образом.

-(void)refreshDisplay
{
    CBLeaderboard* request = [[CBLeaderboard alloc] init];
    [request setCategory:[[sharedGameCenterManager_ classicLeaderboard] category]];
    [request setPlayerScope:GKLeaderboardPlayerScopeFriendsOnly];
    [request setTimeScope:GKLeaderboardTimeScopeAllTime];

    static NSRange kRequestRange = NSMakeRange(1, 3);
    [request setRange:kRequestRange];

    __block GJGameOver* blockSelf = self;
    [display_ displayScoresWithRequest:request completionHandler:^
    {
        CGSize displayContentSize = [blockSelf->display_ contentSize];
        displayContentSize.width = width(blockSelf) - 2.0 * kGJLabelPadding;
        [blockSelf->display_ setContentSize:displayContentSize];

        CGFloat displayHeight =
            bottomEdge(blockSelf->multiplierLabel_) - topEdge(blockSelf->menu_) - 2.0 * kGJLabelPadding;
        CGFloat displayScoreDisplaysCount = [blockSelf->display_ scoreDisplaysCount];
        CGFloat displayLabelPadding =
            (displayHeight - [blockSelf->display_ minContentSize].height) / displayScoreDisplaysCount;
        [blockSelf->display_ setLabelPadding:MIN(floor(displayLabelPadding), kGJLabelPadding)];

        static CGFloat kFadeInDuration = 2.0;
        if ([blockSelf->display_ opacity] == 0)
            [blockSelf->display_ runAction:[CCFadeIn actionWithDuration:kFadeInDuration]];
    }];

    [request release];
}

Моя игра вылетает, когда и слой, и, следовательно, дисплей освобождаются, а запрос не выполнен. Когда запрос завершается, он пытается отправить сообщение освобожденному экземпляру, что приводит к сбою. Можно ли отменить запрос таблицы лидеров? Если нет, есть ли способ избежать сбоя, не вызывая утечки памяти?


person Sam Hertz    schedule 12.03.2013    source источник
comment
Вам нужно __weak в дополнение к __block в контексте ARC, чтобы исключить циклы сохранения. Кроме того, ничто из этого не является явным потокобезопасным, чего GameKit требует от обратных вызовов.   -  person CodaFi    schedule 12.03.2013
comment
О, Боже. Ну, тогда это может стать немного волосатым. Вашему потоку и, следовательно, блоку нужна копия себя, чтобы не происходило ничего из этого преждевременного освобождения. Я бы избавился от квалификатора блока и позволил бы блоку сохранять копию себя, как бы странно это ни звучало.   -  person CodaFi    schedule 12.03.2013
comment
@CodaFi Спасибо. Я сам об этом думал. Надеюсь, дело все равно произойдет. Придется отлаживать и проверять.   -  person Sam Hertz    schedule 12.03.2013
comment
Или вы можете транслировать уведомление. Больше никаких циклов сохранения, и если self пошел по пути динозавров, это будет No-Op (пока вы отменяете регистрацию в Dealloc)   -  person CodaFi    schedule 12.03.2013
comment
@CoadFi Можете ли вы уточнить? Я могу придумать решение, которое устанавливает некоторую глобальную переменную в Dealloc, которую затем может прочитать блок, но такое решение, мягко говоря, уродливо: D   -  person Sam Hertz    schedule 12.03.2013
comment
Я говорю, что вы можете транслировать NSNotification в класс вместо того, чтобы явно использовать себя и создавать циклы сохранения. Тогда не имеет значения, существует объект или нет, важно, подписан ли объект на уведомление, и это гораздо более контролируемо.   -  person CodaFi    schedule 12.03.2013
comment
На данный момент я исправил проблему, проверив, есть ли у отображения таблицы лидеров родительский элемент в обработчике завершения. В любом случае, здесь есть более фундаментальная проблема. Эти запросы выполняются каждый раз, когда появляется экран окончания игры. Поскольку потенциально игрок может циклически переключаться между игрой и игрой через экран много раз, может быть много запросов, которые останутся невыполненными (я заметил, что Game Center любит пробовать, пока есть подключение). Кажется, мне может понадобиться делегировать запросы синглтону, который может ограничить их общее количество.   -  person Sam Hertz    schedule 12.03.2013


Ответы (1)


В обоих ваших блоках вы используете __block, чтобы позволить блоку ссылаться на self, не сохраняя его. Это проблема, потому что вы выполняете асинхронную операцию, и если блок выполняется после того, как self был освобожден, он использует висячий указатель. Весь смысл блоков, сохраняющих захваченные ими объекты, состоит в том, чтобы поддерживать их жизнь, чтобы блок мог их использовать.

Обычно не сохраняют self при создании блоков, чтобы избежать циклов сохранения. Однако я не вижу здесь циклов сохранения:

  • request_ в displayScoresWithRequest, вероятно, сохраняет блок в displayScoresWithRequest
  • Блок в displayScoresWithRequest сохраняет self, объект CBLeaderboardDisplay
  • Блок в displayScoresWithRequest сохраняет блок из refreshDisplay
  • Блок в refreshDisplay сохраняет self, объект GJGameOver
  • Объект GJGameOver сохраняет display_, объект CBLeaderboardDisplay

Однако объект CBLeaderboardDisplay не сохраняет свою переменную экземпляра request_. (Этот код очень плохо написан, так как request_ высвобождается в конце метода, но не устанавливается в nil. Вероятно, его следует сделать локальной переменной или что-то в этом роде. И следует использовать логический флаг, если вы хотите проверить, является ли код запускался один раз или нет.)

person newacct    schedule 13.03.2013
comment
Я согласен, не было бы никаких циклов сохранения, если бы каждому блоку было разрешено сохранять свое собственное «я». Тем не менее, меня больше всего беспокоит игрок, который постоянно выходит на экран с окончанием игры. Это приведет к тому, что многие запросы могут остаться без ответа (я видел, как это происходило во время отладки). Я решил эту проблему, делегируя запросы объекту, который сохраняется в памяти, и рассылая уведомления. Я думаю, что фраза «плохо написано» немного резковата. Я просто использую request_, чтобы указать, существует ли исполняемый запрос. С тех пор я изменил эту переменную на BOOL. - person Sam Hertz; 13.03.2013