Продолжаю краткий обзор особенностей Objective-C, удивляющих Java программистов (первая часть). Во второй части немного будет рассказано о философии языка, о поддержке пространств имен и о типе id. Судя по комментариям к первой части, необходимо немного пофилософствовать, поэтому обещанные свойства (property) переносятся в третью часть. Также узнаем какие вопросы задают в американских супермаркетах.
Совершенство это не когда нечего добавить, а когда нечего отнять
Как правильно было отмечено bobermaniac в комментариях к первой части — синтаксис не очень важен, важнее понимать идиомы языка. Не менее важно понимать и общие принципы построения языка, его философию. Самого дрожь берет от высокопарности.
Я начал с заезженной цитаты, но она отлично характеризует дизайн языка С. Простота, выразительность, лаконичность и все это без ущерба возможностям. Создатели С верили в людей — в результате в споре компилятора и программиста всегда побеждает последний (хотя и не всегда конечный пользователь этому рад). Но потом программисты захотели работать с объектами. И на компактный фундамент С водрузили колонны объектной ориентации. Наибольших успехов в этом добился С++. Но формально используя С как основу, С++ не следует его духу минимализма. Разница в толщине «Кернигана-Ричи» и «Страуструпа» наводит на размышления.
Брэд Кокс (Brad Cox) хотел добавить возможности SmallTalk в С, не создавая полностью новый язык. У него это получилось и созданный им Objective-C является надмножеством языка С, сохраняя дух его дизайна и в объектных расширениях. Т.е. язык С оставлен как есть, объектно-ориентированные нововведения сделаны в четко оговоренных границах и их не так много. Отчасти это объясняет непривычный синтаксис Objective-C? все эти [] и @ — это те желтые ленты, которые огораживают зону строительных работ (или желтые ленты вешают только на месте преступления?). Один характерный пример минимализма — в Objective-C даже нет специального синтаксиса для создания объектов, как нет его и для определения конструктора и деструктора. Методы alloc, init, new, dealloc являются не частью языка, а частью фреймворка.
Создатели Java пошли немного другим путем и основное внимание уделили безопасности языка, введя более строгую проверку соотвествия типов, проверку выхода за границы массива, обязательную обработку и спецификацию исключений и много другое. Вера в человека была заменена верой в бездушный компилятор. Поэтому программистам на Java очень важно помнить, что Objective-C, как и С, гораздо меньше ограничивает автора, часто в ущерб безопасности кода. Я даже говорю не об управлении памятью, а таких особенностях языка, как категории, непроверяемые компилятором исключения, неформальные протоколы, отсутствие гарантии существования метода (не всегда, но бывает). При желании по своим ногам можно палить с двух рук очередями.
Теперь давайте проследуем за настроженно озирающимся Java программистом и рассмотрим пару практических вопросов.
Do you have id?
Подобный вопрос можно услышать при покупке алкоголя в Северной Америке. В ответ следует показать удостоверение личности с данными о возрасте (обычно водительские права). Я первый раз услышал «idea» в вопросе и удивленно ответил, что собираюсь безыдейно выпить все 6 бутылок пива. Продавец осторожно потянул упаковку к себе.
В Objective-C тоже есть тип id — универсальная ссылка на объект любого класса. Если Java программист подумал, что это напоминает ссылку на Object, то он ошибается. Если С программист подумал, что это напоминает void*, то он ошибается. Компилятор рассматривает id именно как ссылку на объект любого класса. Можно вызывать любой ранее определенный метод любого класса (но если метод не определен — нельзя), присваивать объект любого класса без приведения типов и т.п. Единственное ограничение — нельзя обращаться к полям объектов.
Еще раз напомню структуру классов из предыдущей статьи
@interface Profile : NSObject @property (readonly) int version; @end @interface Feature : NSObject - (Profile*) getProfile:(NSString*)name; @end @interface Phone : NSObject + (Phone*) designAndProduce:(NSString*)name; -(Feature*) getFeature:(NSString*)name; @end
Теперь несколько примеров использования id:
// Можно так id idPtr = [Phone designAndProduce:@"iphone5"]; // Можно и так Phone* anotherPhone = idPtr; // Можно даже так (с ошибкой во время выполнения программы) Feature* badFeature = idPtr; // Так нельзя - компилятор скажет, что сообщение getFeture не определно и будет прав Feature* anotherBadFeature = [idPtr getFeture:@"bt"]; // А так можно Feature* btFeature = [idPtr getFeature:@"bt"]; // Даже так можно, сообщение существует, компилятор нам верит, а зря Profile* profile = [idPtr getProfile:@"a2dp"];
В последнем случае при запуске программы мы получим сообщение об ошибке: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Phone getProfile:]: unrecognized selector sent to instance 0x100114510'
и начнем немного лучше понимать как работает диспетчеризация сообщений.
Plastic or paper?
Еще один хороший вопрос от кассиров американских супермаркетов. Спрашивают какой именно пакет дать, но с первого раза мало кто догадывается.
Так вот, о пакетах. Неприятная новость для Java программистов — пакетов (packages) здесь нет. Нет вообще никаких механизмов управления пространствами имен. Можно только выбирать какие заголовочные файлы включать, а какие нет. Скажем спасибо минимализму С и консервативности авторов Objective-С. Чтобы избежать конфликтов имен рекомендуется использовать префиксы. Например, NS в имени класса NSObject — это префикс классов, разработанных для ОС NextStep компании NeXT. Последнюю основал Стив Джобс в перерыве между работой в компании Apple и работой в компании Apple.
Нужно помнить одно важное отличие от Java. JVM доступны все классы платформы Java, все классы добавленные пользователем в classpath и/или lib/ext и кое-что еще. Поэтому конфликта имен избежать очень сложно. Программа на Objective-C имеет дело только с классами, использующимися в коде, так что вероятность конфликта на порядок меньше. Это объясняется разной семантикой import в Java и #import в Objective-C. Первый импортирует пространство имен, позволяя использовать короткие имена классов в коде. Но сам класс доступен и без этого, придется лишь писать полное имя класса (т.е. не List, а java.util.List). В Objective-C происходит настоящий импорт определения класса, без него компилятор не имеет никаких знаний о данном классе.
Иногда полезна полумера — директива @class позволяет объявить класс без его определения.
@class Profile @interface Feature : NSObject - (Profile*) getProfile:(NSString*)name; @end
В этом случае мы уверяем компилятор, что такой класс существует и он соглашается откомпилировать наш код. Т.к. компилятор все равно ничего не знает о структуре класса, посылка сообщений объектам этого класса вызовет ошибку компиляции. Но это обычно и не надо, т.к. директива обычно используется в заголовочных файлах. Возникает вопрос — зачем вообще она нужна, если можно обойтись #import? Обычно для оптимизации компиляции в больших проектах, т.к. импорт может сильно увеличить объем кода и, соответственно, время компиляции. Вторая причина — цикличные ссылки между классами.
Заключение
Надеюсь, единственный оставшийся после прочтения вопрос — при чем тут американские супермаркеты? Не знаю, просто такие подзаголовки получились.
Автор: hashmap