Модификация изменяемого объекта в обработчике завершения - PullRequest
6 голосов
/ 17 августа 2011

У меня есть вопрос о безопасности потоков в следующем примере кода от Apple (из руководства по программированию GameKit)

Для загрузки достижений из игрового центра и сохранения их локально:

Шаг 1) Добавьте изменяемое свойство словаря в ваш класс, который сообщает о достижениях. В этом словаре хранится коллекция объектов достижений.

@property(nonatomic, retain) NSMutableDictionary *achievementsDictionary;

Шаг 2) Инициализируйте словарь достижений.

achievementsDictionary = [[NSMutableDictionary alloc] init];

Шаг 3) Измените код, загружающий данные о достижениях, чтобы добавить объекты достижений в словарь.

{
    [GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error)
        {
            if (error == nil)
            {
                for (GKAchievement* achievement in achievements)
                    [achievementsDictionary setObject: achievement forKey: achievement.identifier];
            }
        }];

Мой вопрос таков: объект достижений изменяется в обработчике завершения без каких-либо блокировок сортировки. Разрешено ли это, потому что обработчики завершения являются блоком работы, который гарантированно будет выполняться iOS как единое целое в главном потоке? И никогда не сталкивались с проблемами безопасности потоков?

В другом примере кода Apple (GKTapper) эта часть обрабатывается по-другому:

@property (retain) NSMutableDictionary* earnedAchievementCache; // note this is atomic

Тогда в обработчике:

[GKAchievement loadAchievementsWithCompletionHandler: ^(NSArray *scores, NSError *error)
        {
            if(error == NULL)
            {
                NSMutableDictionary* tempCache= [NSMutableDictionary dictionaryWithCapacity: [scores count]];
                for (GKAchievement* score in scores)
                {
                    [tempCache setObject: score forKey: score.identifier];
                }
                self.earnedAchievementCache= tempCache;
            }
        }];

Так почему же другой стиль, и является ли один способ более правильным, чем другой?

1 Ответ

2 голосов
/ 06 сентября 2011

Это разрешено, потому что обработчики завершения являются блоком работы, который гарантированно будет выполняться iOS как единое целое в главном потоке? И никогда не сталкивался с проблемами безопасности потоков?

Это определенно не тот случай. Документация для -loadAchievementsWithCompletionHandler: явно предупреждает, что обработчик завершения может быть вызван в потоке, отличном от того, из которого вы начали загрузку.

«Руководство по программированию потоков» от Apple классифицирует NSMutableDictionary среди классов, небезопасных для потоков, но квалифицирует это следующим образом: «В большинстве случаев вы можете использовать эти классы из любого потока, если вы используете их только из одного потока за раз . "

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

Последний пример выглядит так, как будто он полагается на поддержку атомарных свойств для переключения между старым и новым кешем. Это должно быть безопасно, при условии, что весь доступ к собственности осуществляется через средства доступа, а не через прямой доступ к ивару. Это связано с тем, что методы доступа синхронизируются друг с другом, поэтому вы не рискуете увидеть половину установленного значения. Кроме того, получатель сохраняет и автоматически высвобождает возвращаемое значение, поэтому код со старой версией сможет завершить работу с ним без сбоев, поскольку он был выпущен в середине своей работы. Неатомный метод получения просто возвращает объект напрямую, что означает, что он может быть освобожден из-под вашего кода, если новое значение было установлено для этого свойства другим потоком. Прямой доступ к ivar может столкнуться с той же проблемой.

Я бы сказал, что последний пример является и правильным, и элегантным, хотя, возможно, немного чрезмерно тонким без комментариев, объясняющих, насколько важна атомарность свойства.

...