2012-04-25

ObjC vs Java

Серия постов о том, чего, по мнению Java-программиста, не хватает в ObjectiveC.
Ну, и мои мысли по этому поводу.
TL;DR: Отсутствие namespace и ручное управление памятью -- зло. ObjC -- не мой любимый язык.
  1. дженерики  для динамического языка не очень критичны (в питоне, к примеру, их нет, а в ActionScript -- есть всего один дженерик)
  2. Нет абстрактных методов. Согласен. С точки зрения "классического" ооп, отсутствие abstract и protected методов не дает задать контракт между базовым и дочерними классами. Приходится извращаться с методами, бросающими исключение -- что работает только при нормальном покрытии юнит-тестами. 
  3. Хидер+Реализация(*.h/*.m) vs один файл. Спорное высказывание. Хотя с одним файлом работать и удобнее, наличие отдельно вынесенного публичного интерфейса есть хорошо.
  4. Пространства имен. ППКС. Отсутствие пакетов или пространств имен в объектно-ориентированном языке -- это, мягко говоря, извращение. 
  5. Исключения.  В 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++.


2012-04-24

Эльфы 14-го уровня среди нас. Чувствую себя лузером со своими 4/115.

2012-04-20

[trick] чтение из /dev/[u]random

Баг: генератор ключей выдает один и тот же ключ, если интервал между запусками достаточно мал.

Лезу в код, вижу примерно такое.

template <typename T>
RandomGenerator::RandomGenerator() {
  T seed = 0;
  {
    std::ifstream in("/dev/urandom");
    in >> seed;
  }
  if (!seed)
    seed = clock(NULL);
  ... // далее seed используется, чтобы инициализировать один из бустовских ГПСЧ.
}

Далее этот класс инстанциируется для T = uint32_t.
Первое, что бросается в глаза, что /dev/urandom мы открываем как текстовый файл. А потом пытаемся читать из него 4хбайтное целое.

Добавил ios::binary -- лучше не стало.

Вынес в отдельный файл, стал экспериментировать.
Не читается uint32 из '/dev/random'.
Поменял тип на char - читает.
На short - опять не читает.
И такое поведение одинаково на osx, linux и freebsd.

А потом я вспомнил, что /dev/[u]random - это character device.

2012-04-18


Гуглил доку, случайно нажал "Перекласти цю сторінку"
Заголовок таба "какао - миша вниз" вынес мозг. 

о копипасте.

На программистcкой клавиатуре сочетание Ctrl+C (Cmd+C) должно бить током.