Как понять Objective C: вызовы методов

Когда здоровый программист впервые видит вызовы методов в Objective C — у него выпадают глаза. Давай поговорим об этом.

Теория

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

PHP Javascript
$image->calculateSize(array(
    'image' => $image,
    'width' => 50,
    'height' => 50
));
image.calculateSize({
    image: image,
    width: 50,
    height: 50
});


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

В Objective C создатели решили закрепить это правило на уровне синтаксиса языка: каждый параметр имеет название. Оно указывается как при вызове, так и при определении метода. Таким образом, жестко внедряется практика, когда программисту приходится думать над сутью вещей, а не просто дубасить код.

Но большое количество методов вызываются с единственным параметром. В этом случае, очевидно, не имеет смысла его отдельно именовать, т.к. из самого названия метода обычно все понятно.

$records->storeData('sirko.db');

$users->findNickname('vasya');


Для этого случая в Objective C существует упрощенный синтаксис, который обычно и вводит в ступор новичков. Первый параметр метода названия не имеет. Вот так: все параметры названы, а первый — нет. В каком-нибудь из скриптовых языков это могло бы выглядеть, скажем, как-то так:

PHP Javascript
$this->calculateSize($image, array(
    'width' => 50,
    'height' => 50
));
this.calculateSize(image, {
    width: 50,
    height: 50
});


Я думаю, что это — самая главная проблема в постижении Objective C. Опытный программист интуитивно поймет логику практически любого языка программирования, но без вот этого «секретного знания» сходу разобраться в Objective C врядли можно.

Получился интересный побочный эффект. Уж не знаю, специально ли так задумано или исторически сложилось, но название первого параметра стали выносить в название метода.

Например: findUserByNickname, locateByCoordinates, storeIntoFilename, loadFromTable, setupById и так далее. Такая система именования здесь полностью прижилась, в результате чего код на Objective C в среднем неплохо читаемый.

Практика

А теперь — слайды.

content = [answersTable findById:42];  


Вызов метода в Objective C пишется в квадратных скобках. В начале идет переменная объекта. Затем — название метода и его параметр через двоеточие. answersTable — экземпляр какого-нибудь замечательного объекта, findById — метод, а 42 — это хорошая цифра. Результат возвращается в переменную content. Мнемоническое правило: квадратные скобки заменяются на вычисление содержимого. (Как в Tcl).

В других языках это могло бы выглядеть так:

$content = $answersTable->findByid(42);


А вот так выглядит само определение метода в замечательном объекте:

- (AnswerContent *) findById: (int) questionId {
    ...
    [self doSomething:questionId];
    ...
}


С минуса начинается определение метода объекта. В скобках — возвращаемый тип данных, указатель, как в C: (AnswerContent *). Проще говоря, это значит, что метод вернет объект типа AnswerContent. Далее идет название метода (findById) и через двоеточие — переменная первого параметра, также с обязательным указанием ее типа, тоже как в C: (int) questionId.

Внутри метода можно обращаться к переменной questionId.

В других языках это могло бы выглядеть так:

PHP Javascript
public function findById($questionId) {
    ...
    $this->db->get($questionId);
    ...
}
Something.prototype.findById  = function(questionId) {
    ...
    this.db.get(questionId);
    ...
}


Теперь — последний и самый сложный шаг в понимании вопроса.

masterpiece = [gallery findImageByWidth:400 andHeight:300];


Вот так вызывается метод с двумя параметрами. Первый ("400") названия не имеет, указывается сразу после имени метода. Второй имеет название: andHeight. Обрати внимание, как элегантно назван метод и его параметры. Через некоторое время привыкаешь так писать и читать и проговариваешь про себя: «gallery, pls find image by width and height».

Параметров может быть множество:

price = [trade calculateWithPrice:25.55 volume:500 value:3 ticker:aaplTicker];


Внутри квадратных скобок — только вызов одного метода у одного объекта, не запутаешься.

В другом языке программирования это могло бы звучать так:

PHP Javascript
$price = $trade->calculate(array(
    'price' => 25.55,
    'volume' => 500,
    'value' => 3,
    'ticker' => $aaplTicker
));
price = trade.calculate({
    price: 25.55,
    volume: 500,
    value: 3,
    ticker: aaplTicker
});


А вот так выглядит определение этого метода:

- (float) calculateWithPrice:(float)price volume:(int)volumeAmount
    value:(int)value ticker:(TickerClass *)ticker {
    ...
}


Точно также указывается минус, тип возвращаемых данных, название метода, тип и переменная первого параметра. Затем, через пробел — название следующего параметра, его тип и переменная. И так далее.

Важный момент. Внутри метода названия параметров не используются. Чтобы проиллюстрировать это, я назвал второй параметр volume, но он помещается в переменную volumeAmount. Внутри метода можно к ней обратиться:

volumeAmount+=10;


Но если ты захочешь обратишься к volume — компилятор будет возражать, такой переменной не существует. А вот параметр с названием price помещается в переменную с таким же названием. Все просто:

price = price * 0.90; // a better discount for our customer


Чаще всего названия переменных и параметров для простоты так и пишут одинаково.

И на десерт — ничего не возвращающий метод без параметров:

Вызов:
[self destroy];


Определение:
- (void) destroy {
    ...
}


Выводы

Такой способ вызова методов, естественно, поначалу вызывает отторжение. Это нормально. Без секретного знания про первый параметр прочитать сходу код на Objective C невозможно, а это вызывает раздражение, причем даже больше у опытных программистов. А накопленное раздражение, в свою очередь, моментально ассоциируется с его источником.

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

А опыт программирования на Objective C принесет в твою практику, среди всего прочего, новую культуру именования вещей. А от этого выйграет любой код на любом языке.

PS для опытных специалистов: это статья для начинающих. Поэтому я несколько утрировал смысл, чтобы сфокусироваться на главном. В частности, я не упомянул, как на самом деле называются методы и так далее.

Эта статья писалась для Хабрахабра: Понять Objective C: вызов методов. Я также опубликовал ее на своей домашней страничке: Понять Objective C: вызов методов.

Таже штука и в Ruby присутствует, поначалу не понятно но когда начинаешь использовать то все выходит так аккуратно и красиво записано что одно заглядение. Могу ошибатся но кажется корни такой записи растут из Smalltalk.
вот если бы ещё для iPhone написали бы компилятор под Windows :) чтобы писать для иФонов программы не надо было бы покупать Mac, было бы просто чудесненько )))
не надо ))) поставил я себе Mac ) буду потихоньку начинать изучать Objective-C и пробовать писать для iPhone )
Егор, ты неправ. ;-)
Во первых в Objective С нет вызова методов. Есть передача сообщений. Т.е. вообще говоря, Objective-C не стековый. Это дает много-много вкусняшек.
Читаем, например, здесь http://www.chachatelier.fr/programmation/fichiers/cpp-objc-en.pdf

3.4.1 Sending a message to nil
3.4.2 Delegating a message to an unknown object
и самое вкусное:
3.4.3 Forwarding: handling an unknown message

Максим, ты неправ ;-)

Читать, например, здесь:

PS для опытных специалистов: это статья для начинающих. Поэтому я несколько утрировал смысл, чтобы сфокусироваться на главном. В частности, я не упомянул, как на самом деле называются методы и так далее.

:)
Мне, когда я привыкал к Objective-C, со слов Sending message стало все намного понятней. Может быть после сигналов-слотов Qt.
Можно представить Objective-C класс как C++ класс у которого наружу торчит только один метод принимающий указатель неа один базовый тип. Внутри этого метода происходит востановление типа и свитч по typeid. Соответсвенно декаларацию метода можно представить как "временное" объявление структуры-сообщения.
Для цепепешника так понятнЕЕ буде ИМХО. ;o)

PS Я вот полгода руководил проектом на Андроиде и только сейчас узнал что он не Java. ;-)
на лиспе удобней всё равно ;Р
Давно уже не писал на Objective-C. Но что для меня осталось в нем загадкой - garbage collector.
Мои приложения которые выехали на Apple Store доводились "тупым подбором варианта при котором не течет и не падает". ;-)
Для C++ програмиста он достаточно не очевидный.
Это непрофессиональный подход.

Надо просто вникнуть в GC. Это не то чтобы совсем просто, но постижимо за несколько часов вдумчивого чтения, после чего вопросов не будет и MM станет прозрачным и понятным.

А сейчас вообще появился ARC, так что все стало еще проще.

PS: только не "Apple Store", а как-то иначе.
Это как раз профессиональный подход. Потому что "методика" изученая по оффициальной доке приводила к нарушению цикла жизни объектов и падению.

Сделать все ручками, а потом проверить, что там где нужно создается и удаляется - это намного более правильно:
Неважно - правильно ли написана документация.
Неважно - правильно ли реализовано это в системе.
Неважно - правильно ли понято это програмистом.
Важно - коректная, ожидаемая работа приложения.

Это самый грамотный подход. Например, меня он спас при портировании EFL на ThreadX. Когда выяснилось что у "почти pthread совместимой RTOS" malloc в libc забит NOP-ами.
Так же, это единственный вариант при котором я считаю необходимиым юнит тестирование. Только возникло сомнение - пиши юнит тест с полным покрытием веток. Возможно даже не на весь алгоритм, а на его модель.

PS ;-) В смысле App Store. Думал одно - написал третье.

PPS Выше речь шла про эту казуалку http://itunes.apple.com/us/app/star-cargo-3000/id360993519?mt=8