Pull to refresh

Циклические контейнеры в Objective-C

Development for iOS *Compilers *Objective C *Development for MacOS *
Translation
Original author: AlexDenisov
Некоторое время назад я написал этот код:

NSMutableArray *environments = [NSMutableArray new];
for (NSString *key in [dictionary allKeys]) {
    XCCEnvironment *environment = [[XCCEnvironment alloc] initWithName:key
                                                            parameters:dictionary[key]];
    [environments addObject:environments];
}
return environments;

Заметили проблему? Я — нет.

Проблема


После запуска программы я словил крэш:

[__NSArrayM someSelector]: unrecognized selector sent to instance 0x100211d80

Обработчик 'environments' ожидал 'XCCEnvironment', а получил 'NSMutableArray'.

Поначалу было непонятно почему это произошло, но когда я присмотрелся к коду, то заметил, что я просто кладу массив в этот же массив:

// ...
NSMutableArray *environments = [NSMutableArray new];
// ...
[environments addObject:environments];
// ...

Документация ничего не говорит о поведении коллекций в таких ситуациях, единственный полезный материал который я нашел по этому поводу — статья Майка Эша (Mike Ash) Let's break Cocoa.

Статья утверждает что мутабельные массивы, словари и множества сходят с ума если создать так называемый циклический контейнер. Кроме того при включенном ARC появляется утечка памяти — коллекция удерживает сама себя.

Решение


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

Я был абсолютно уверен что clang может предотвратить эту ошибку, но не нашел никаких параметров/настроек которые включают эту проверку.

В конце концов я решил добавить эту проверку. Реализация заняла пару вечеров, но теперь она находится в trunk.

Патч включает в себя проверку следующих коллекций:

  • NSMutableArray
  • NSMutableDictionary
  • NSMutableSet
  • NSMutableOrderedSet
  • NSCountedSet

и показывает предупреждение если вы пытаетесь добавить коллекцию внутрь ее самой.
Предупреждение может быть включено/выключено посредством флагов '-wobjc-circular-container'/'-wno-objc-circular-container' соответственно, хотя оно включено по-умолчанию.

Заключение


Версия clang из trunk'а содержит эту функциональность, но она еще недоступна в Xcode, я полагаю что появится она там после следующего мажорного релиза, может быть через год.

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

Happy hacking!
Tags:
Hubs:
Total votes 15: ↑14 and ↓1 +13
Views 7.5K
Comments Comments 6