О чем эта статья?
Два факта. В мире много Java программистов. Популярность Objective-C растет. Вывод: Java программист, изучающий Objective-C не такая уж редкость. Если знать ключевые различия между языками, то можно эффективно использовать существующие знания Java и быстрее начать писать на Objective-C.
Я начинал с С, пишу на Java последние 15 лет, иногда переключаясь на С++, Python, Perl, с интересом наблюдаю за Scala. И вот теперь Objective-C.
От каждого путешествия обычно остается несколько историй о наиболее забавных отличиях «нас» от «них». Не претендуя на полноту изложения расскажу об особенностях Objective-C, которые особенно удивили меня, как пришельца из Java. Позднее я заметил, что непонимание или неприятие этих особенностей порождает код на Java, написанный синтаксисом Objective-C, тогда как гораздо естественнее писать просто код на Objective-C. Язык это не только синтаксис, это в первую очередь идиомы. Еще раз подчеркну — это не путеводитель, а заметки туриста. Если они покажутся интересными — продолжу рассказывать о своих наблюдениях в последующих статьях.
Посылаем, а не вызываем, сообщения, а не методы
Java:
object.method(param);
Objective-C:
[object method:param];
В Java мы вызываем методы объекта, в Objective-C — посылаем сообщения объекту. Разница не только в терминологии. Любое сообщение проходит через функцию-диспетчер, которая использует параметры, помещенные в стек и идентификатор метода (использую слово метод, чтобы обозначить код, обрабатывающий сообщение и продолжу его использовать, чтобы было привычнее для Java программистов), называемый селектор (selector), чтобы найти и вызвать метод объекта-получателя. Звучит как ненужное усложнение, но эта дополнительная абстракция фундаментальна и обеспечивает динамизм, частные проявления которого описаны ниже.
Замечу, что в отличие от Java компилятор не всегда может проверить наличие метода, в таких случаях он выдает предупреждение (warning). Если уверены в себе — игнорируйте. Если взяли на себя слишком много — программа свалится на посылке сообщения.
Как меня зовут?
Java:
public Feature addFeature (String name, String value, boolean isOptional);
Objective-C:
-(Feature*) addFeature :(NSString*)name withValue:(NSString*)value asOptional:(BOOL) optional:
Имя метода в Objective-C “размазано” по списку аргументов. Полное имя, а именно оно используется при конструировании селектора, звучит как addFeature:withValue:asOptional:SEL selector = @selector(addFeature:withValue:asOptional:);
Если сдержать первые позывы, начинаешь привыкать и оказывается, что такие имена хорошо читаются и даже ясно, зачем нужен каждый из 16 параметров. Хотя это не причина делать метод с 16 параметрами.
Серьезный шок: любой может испортить мой класс, а не только я
В Java аналогов нет
Objective-C:
@interface NSString (LetsAugment)
-(BOOL) isGoodActor;
@end
Но и мы в ответ можем испортить что угодно, хоть NSObject. Спасибо категориям (category). В примере выше определена категория LetsAugment, которая добавила метод isGoodActor базовому классу NSString. Пусть реализация возвращает NO (false в Java), если это не актер или актер, но плохой и YES (true) если актер, да еще и хороший. Домашнее задание — угадайте результат работы этого кода:if ([@”Steven Seagal” isGoodActor]) NSLog (@”Yeah!”) else NSLog (@”What's wrong with you?”);
Все дело в динамической диспетчеризации сообщений, вот она динамика, вот она гибкость.
Категории, во-первых, позволяют разбить определение класса по нескольким файлам. В идеале класс должен выполнять одну конкретную роль, но иногда это не так, например в случае реализации фасада и некоторых других шаблонов. В этом случае удобно описать его в нескольких файлах. Во-вторых, можно действительно элегантно добавить отсутствующую функциональность к существующему классу.
Совсем шокирующее: неинициализированные ссылки не портят почти ничего
Java:
MyTao tao = null;
int howManyHands = tao.clap();
Objective-C:
MyTao* tao = nil;
int howManyHands = [tao clap];
В первом случае мы, конечно, получим NullPointerException и, возможно, увольнение. Во втором — в howManyHands будет возвращено 0. Это не 1, как мы ожидали, но хоть не уволили.
Опять все дело в динамической диспетчеризации сообщений. Диспетчер сообщения сразу же проверяет получателя объекта и если он nil (null в Java), то возвращает типизированную пустоту (nil, 0, NO и т.п.). Звучит как жалкая защита от дурака, но на самом деле разумное использование этой особенности позволяет писать очень компактный и читаемый код.Java: (псевдокод)
class Profile {
int getVersion() {
...
}
class Feature {
Profile getProfile(String name) {
...
}
class Phone {
Feature getFeature(String name) {
...
}
class PhoneFactory {
static Phone designAndProduce(String model) {
...
}
}
...
int version = 0;
Phone phone = PhoneFactory.designAndProduce(“iphone5”);
Feature btFeature = phone.getFeature(“bt”);
if (btFeature != null) {
Profile profile = btFeature.getProfile(“a2dp”);
if (profile != null) {
version = profile.getVersion();
}
}Objective-C:
@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
...
Phone* phone = [Phone designAndProduce:@"iphone5"];
Feature* btFeature = [phone getFeature:@"bt"];
Profile* profile = [btFeature getProfile:@"a2dp"];
int version = profile.version;
Код на Objective-C гораздо более чистый и при этом безопасен как и Java эквивалент. Если [phone getFeature:@«bt»] вернет nil, исполнение продолжится и version будет иметь значение 0.
Почему же «не портят почти ничего»? Все же nil не является полностью безопасным, диспетчер защищает нас при отправлении сообщений, но он бессилен, если мы обращаемся к полям объекта. Кстати именно поэтому важно обращаться к полям через методы (getter/setter). Более того, Objective-C предлагает для этого специальную конструкцию языка (кто-то обратил внимание на profile.version?) — свойства (property), но об этом в следующей статье.