Серия постов о том, чего, по мнению Java-программиста, не хватает в ObjectiveC.
Ну, и мои мысли по этому поводу.
TL;DR: Отсутствие namespace и ручное управление памятью -- зло. ObjC -- не мой любимый язык.
- дженерики для динамического языка не очень критичны (в питоне, к примеру, их нет, а в ActionScript -- есть всего один дженерик)
- Нет абстрактных методов. Согласен. С точки зрения "классического" ооп, отсутствие abstract и protected методов не дает задать контракт между базовым и дочерними классами. Приходится извращаться с методами, бросающими исключение -- что работает только при нормальном покрытии юнит-тестами.
- Хидер+Реализация(*.h/*.m) vs один файл. Спорное высказывание. Хотя с одним файлом работать и удобнее, наличие отдельно вынесенного публичного интерфейса есть хорошо.
- Пространства имен. ППКС. Отсутствие пакетов или пространств имен в объектно-ориентированном языке -- это, мягко говоря, извращение.
- Исключения. В ObjC они более "многословны", чем в других языках, на этом, имхо, все отличия и заканчиваются.
Из других минусов языка автор не назвал многословность (да-да, тот самый синтаксический оверхед :) ). Возможно, потому, что по сравнению с явой он не столь велик.
Защитники ObjC скажут "зато имя метода отражает, что он делает", но методы вида
- (NSRect)splitView:(NSSplitView *)splitView effectiveRect:(NSRect)proposedEffectiveRect forDrawnRect:(NSRect)drawnRect ofDividerAtIndex:(NSInteger)dividerIndex или константы длиной в 65 символов это тот случай, когда, сфокусировавшись на деревьях, не видно леса.
Кроме длинных идентификаторов, многословность вызвана еще одним недостатком языка:
Авторы языка так и не смогли определиться с управлением памятью.
В Managed языках все просто -- там изначально есть GC.
В C++ есть деструкторы объектов на стеке, что привело к концепции "умных" указателей: сперва пусть кривой, но стандартный std::auto_ptr, потом появились труды Александреску, бустовкие intrusive_ptr и shared_ptr/weak_ptr, которые и стали стандартом сначала де-факто, а потом и де-юре.
Move-semantic в С++11 с одной стороны, была революцией (как писали авторы стандарта -- "мы сломали почти все книги о эффективном программировании на С++"), с другой -- слегка поменялась форма, но не содержание -- заворачиваем объект в умный указатель и расслабляемся.
В ObjC же управление памятью развивалось этапами.
Изначально временем жизни управлял сам программист, создавая объект через метод класса alloc и управляя его жизнью, увеличивая счетчик ссылок через retain и уменьшая его вызовом release. То есть ручное управление временем жизни объектов. И сдесь разработчики допустили еще одну ошибку: они сделали создание объекта двухфазным: сперва выделение памяти (обычно alloc), потом инициализация (init или initWith...). Изначально это было сделано для поддержки кастомного распределителя памяти (allocWithZone:), но, по-моему, этим никто никогда не пользовался. Получаем запись вида
Type * x = [[Type alloc] initWith:y];
И тут появляется хитрая политика владения: если в названии метода есть слова copy, alloc, new, то метод возвращает объект с увеличенным счетчиком ссылок, иначе -- с тем-же. То есть, налицо попытка решения технической задачи административным путем.
Ну, и проблему циклических ссылок никто не отменял.
Потом появилась концепция Autorelease Pool. Объекты, зарегистрированные в нем, удаляются при очистке пула, если на них больше никто не ссылается. Пулы образуют свой стек, что позволяет при выходе из метода, к примеру, подчистить за собой (ну, если не забыть вызвать у пула drain или release];
У стандартных классов появились методы вида [Type typeWithY:y], возвращающие объект в полумертвом состоянии (уже зарегистрированным в текущем авторелиз пуле)
И вместо того, чтобы переделать стандартную библиотеку, объявив цепочку alloc-init deprecated, разработчки языка пошли по легкому пути и просто добавили autorelease в базовый класс.
А теперь сравните код
TypeX * x = [[[TypeX alloc] initWithTypeY:[[[TypeY alloc] initWithY:y] autorelease]] autorelease];
и
TypeX x = TypeY(y); // Если на стеке
// или, если объекты выделять в куче.
std::shared_ptr<TypeX> x = std::make_shared<TypeX>(std::make_shared<TypeY>(y));
В ObjC 2.0 добавили свойства, что позволило не копипастить типичный для геттеров и сеттеров код. Но управление членами класса осталось на том-же уровне (retain в init, release в dealloc, если забыл - то ССЗБ)
Потом был GC, который объявили deprecated быстрее, чем он стал мейнстримом. Ну, не deprecated, но "ARC is preferable now". Automatic Reference Counting - это автоматическая вставка retain/release, если статический анализатор Clang считает, что они тут нужны. Если имена ваших методов следуют административным ограничениям (увеличивающие кол-во ссылок методы содержат слово alloc, copy, new, retain ; не меняющие -- не содержат; плюс несколько макросов/ключевых слов типа __strong или __weak), то все будет хорошо.
Если у вас есть циклические ссылки или кто-то нарушил правила - ой. Хорошо если просто утечка, но может быть и дабл-фри. Который крайне тяжело отлаживать, ибо падает обычно внутри Autorelease Pool.
После того, как некоторое время пописал на ObjC, дико хочется обратно на C++.