Objective-C как первый язык программирования

Данный пост был задуман после того, как на некоторые важные вопросы не было найдено внятных ответов. Я отнюдь не претендую на то, что стал крутым программистом. Нет, всё ещё впереди, но период высиживания уже пройден. Это статья из цикла «Не умеешь сам — научи другого». В смысле, чтобы что-то лучше понять нужно это что-то, кому-то объяснить. Мопед не мой, эта фраза встречалась мной раньше в публикациях на Хабре. Некоторые вещи очень сложно понять. И люди, которые понимают, обычно не могут объяснить начинающему. Может быть меня тоже это ждёт. Это как разговор взрослого и ребёнка. Практически конфликт поколений. Пока мой уровень не перерос в профессионала, нужно изложить моё текущее видение.

В статье, термины которые используются при объяснении различных понятий взяты в кавычки. Статья написана для людей, которые уже научились отличать циклы от массивов и понимают что такое функция и метод.
Если Вы, никогда не программировали, то книга Кернигана и Ритчи (в любом издании), Вам явно будет не по силам. В природе не существует такой силы воли, которая смогла бы заставить Вас дочитать книгу до конца, при этом решить все приведённые задачи.
Очень рекоммендую BecomeAnXcoder. Там всё написано достаточно понятно для людей, которые о программировании знают крайне мало. Информация немного устарела, но для старта сойдёт. Может она Вам уже попадалась на глаза или вы даже скачали её, но отложили в каталог с литературой (ну такой каталог есть у каждого, он занимает десятки гигабайт и всё это мы собираемся прочитать как только появится свободное время, а каталог всё растёт...).
Понятно, что оптимальный вариант — MAC. На худой конец — Хакинтош или виртуальная машина. И даже если процессор не позволяет запустить ничего из вышеизложенного — начать можно прямо в блокноте с последующей компиляцией в коммандной строке.
На многих форумах можно найти популярный вопрос, задаваемый людьми, которые, по-видимому находятся в тяжело-бронированном танке: «Нужно ли программисту Objective-C учить собственно С?». Везде пишут одно и то же. Те кто побородатее и начинали со структурного программирования — говорят, что просто необходимо стать, как минимум MiddleDeveloper'ом в C, и лишь потом искать путь в лютых дебрях ООП, коим является Objective-C. Те кто не знал С, но уже каким-то образом достиг статуса MiddleDeveloper'а, возражают: мол, выучи как кнопки на iPhone рисуются и как им назначить функцию, а матчасть — потом подтянется, по мере необходимости. Категорически заявляю, что выучить как кнопкам назначаются функции, не есть программирование! Я не призываю потратить полгода на понимание чистого С. Но пару недель — месяц уделить стоит. Это даст некоторый бэкграунд Вашим амбициям. По мере обучения уже Objective-C обязательно будут возникать такие структуры кода, что не будет ничего понятно. Поэтому, предлагаю безо всякого фанатизма, но детально ознакомиться с книгой Крупника А.Б. «Изучаем Си». Она раза в 3 больше предыдущей по объему, но читается легко и непринуждённо. В книге опять же, на достаточно доступном уровне, объясняются основные азы программирования. В ней можно узнать как отличить массив от цикла и что такое указатели.
Недавно на Хабре промелькнула статья, где автор приводил кучу литературы, которую советовал прочитать и пойти работать джуниором за хлеб. Если Вы просто прочитаете всю приведенную в той статье литературу, то ничему не научитесь, а если вы сможете перерешать и запомнить опять же всю литературу, то Вам можно будет идти не джуниором, а солидным разработчиком.

Начало: метод класса и метод экземпляра


Что такое метод? Это набор команд/инструкций или одна команда/инструкция, которая может быть применена к объекту и вызывает в нём необходимые процессы. Если так не совсем понятно, то читайте дальше и поймёте. На самом деле тяжело понять что такое метод класса и метод экземпляра. Все говорят: «со знаком „+“ — это метод класса, а вот со знаком „-“ — это метод экземпляра. Понятно?» И в ответ слышат: «Да-а, метод класса и метод экземпляра». Я лично сначала понимал только то что они разные, а вот что и когда применять — осталось не понятным. Википедия упорно повторяет то же самое, что мы слышали и читали.
В подавляющем большинстве случаев, Вы будете работать с методами экземпляра.
Итак, метод экземпляра. Представим, что у нас есть программа, в которой мы будем использовать 2 класса: Primus и Kitchen.
Любой класс (кроме абстрактного) имеет методы и/или функции. Класс может иметь несколько методов со знаком "+" перед названием метода (метод класса) и несколько методов со знаком "-" перед названием метода (метод экземпляра). Вот те методы, которые со знаком "-" мы активно применяем в отношении объектов — экземпляров класса и собственно класса. А методы со знаком "+" можно применять только в отношении самого класса, где этот метод был объявлен.

Что такое экземпляр класса?


Звучит гордо, но непонятно. Кто помнит из школьного курса: в геометрии точка — родитель для круга, прямой и прочих фигур, потому что каждая фигура состоит из множества точек.
Класс для Objective-C, на некоторых ресурсах интерпретируют как объект и наоборот. На самом деле это немного на так. Можно сказать что объект это экземпляр класса. Как конкретный экземпляр стоящего у Вас на кухне примуса — это экземпляр типа «Примус»: с крутилками, горелкой и пр. Это не просто примус — это Ваш примус. Класс — это пример для подражания. Образец-эталон чего-то, что тоже может исполнять некоторые функции благодаря методам класса (со знаком "+"). Вот здесь можно прочитать немного более развёрнуто.
Также есть «протоколы» и «абстрактные классы». Здесь всё более интересно, но порог понимания выше чем у класса и объекта. Различие состоит в том, что «протокол» не может иметь подкласс или дочерний объектно его на наследуют, ему нужно соответствовать. Как требованию при приёме на работу. Применение протоколов в реальной жизни я затрону в этой статье. Абстрактный класс — это такая сущность, которая как бы класс, но не совсем конкретный класс, потому и абстрактный. Например опять же, Ваш домашний примус — он экземпляр класса «Примус», а не какого-то абстрактного класса «Нагревательный прибор». Вы же не можете иметь дома нагревательный прибор без рода и племени (это может быть также камин и батарея). Вот таким образом, класс «Примус» — может быть наследником абстрактного класса «Нагревательный прибор». А уже Ваш примус — экземпляр конкретного определения примуса. И за своими атрибутами — свойствами, методами, протоколами он при использовании обращается к своему классу и сверяет что он может делать что не может и что он вообще может иметь в наличии.
Представим что есть некий класс «Горелка газовая» — наследник абстрактного класса «Нагревательный прибор». Класс «Горелка газовая» будет родительским для «Примуса» и «Газовой плиты». Для того, чтобы приблизиться к программированию, нужно немного абстрагироваться.
В Objective-C есть родительский класс NSObject. В языке программирования он как точка в геометрии. Это идеальный класс: в нём нет ничего лишнего, он ничего конкретного делает, но из него можно создать всё что угодно. Такой вот кирпичик мироздания. Если в Xcode зажать кнопку «Command» и кликнуть на NSObject, то перед Вами откроется содержимое файла NSObject.h. В нём около 200 строк описания различных методов. Есть как методы со знаком "+" перед названием, а также методы со знаком "-". Суть методов со знаком "+" такова что их можно применить только непосредственно к самому NSObject. Таким методом является, например,

+(id) alloc;

и применять его можно только так:

[NSObject alloc];

Любой другой объект в Objective-C по определению — потомок NSObject. В нём реализовываются дополнительные методы, которые придают ему черты индивидуальности. Допустим это NSResponder. У этого потомка — NSResponder’а тоже могут быть дочерние объекты. К примеру UIView. Далее по древу наследования можно в качестве примера взять UIScrollView. Потом — UITableView. В каждом случае потомки обрастают дополнительным функционалом. Кроме того, что они умеют делать что-то своё, индивидуальное, они могут делать всё то что может делать их предок. Это и есть — наследование.
Если же вы не хотите, чтобы Ваш класс-потомок абсолютно точно копировал поведение класса-родителя, то можно переделать любой метод класса — родителя по Вашему вкусу. Это есть — полиморфизм.
В Ваших программах вы будете брать нужный Вам класс и добавляя в него нужные методы — расширять его функционал. Также можно брать созданный Вами класс и делать его потомка, опять же расширяя функционал и одновременно создавая уникальный инструмент, для решения нужной задачи. Описанный механизм называется — создание подклассов «subclass».
Не буду вдаваться в широкие описания методов со знаком "+". Нагляднее будет показать, что применение метода

+(id) alloc;

к любому классу или подклассу выделяет под него нужный объём памяти в ОЗУ компьютера.

NSObject *object =  [NSObject alloc];

Применение метода

-(id) init;

инициализирует объект, выделяет для него место в ОЗУ и собственно после этого объект начинает существовать и с ним можно работать.

NSObject *object = [[NSObject alloc] init];

Эти 2 метода («alloc» и «init») только что создали объект «object». И данный объект является экземпляром объекта или экземпляром класса NSObject. Если применить эти методы по раздельности

NSObject *object = [NSObject alloc];
[object init];

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

NSObject *object = [NSObject alloc];

выделил для него такой адрес в памяти

0x000000000

Так происходит потому что при инициализации, исполняемая среда, всем объектам задаёт значение «nil», чтобы избежать ссылки на место в памяти, заполненное «мусором». Или ссылки на другой конкретный объект, который по факту тоже уже является «мусором», потому что он закончил свой жизненный цикл и уже не «хранится» в памяти, а просто «находится». Для того чтобы понять что такое «мусор», создайте простейшую программку, в которой объявите

int i;

не присваивая ей значение, а потом выведите её значение в консоль

NSLog(@"%i", i);

Вы будете удивлены тем, чему равняется Ваше «i».
Также будет создан экземпляр объекта «isa», который будет рассмотрен позже. Это единственный ненулевой указатель при создании Вашего объекта. Допустим, это ниточка к родительскому объекту, который породит свой экземпляр и даст жизнь нашему объекту.
То есть теоретически объект уже есть после применения метода «alloc». Но он не сможет пройти проверку

if (!self){
...
}

Такой кусок кода можно видеть в любом шаблонном объекте в методе «init» или его производных.
В данном случае, объект вроде как есть, но в принципе он равен «nil». Не инициализированный объект — это как машина, которую Вы только собираетесь купить в кредит. Вроде как есть, но она не Ваша. И если не отходя от кассы применить метод «init»

NSObject *object = [[NSObject alloc] init];

то «Бинго!», и объект принадлежит Вам, с ним можно делать всё что хотите. А вот если к нему применить метод

[object init];

то инициализироваться может другой объект. Вроде как вы выплатили кредит за машину, но оказалось что не за ту машину и Вам вместо одного автомобиля дают другой. Они на первый взгляд не отличимы друг от друга. Но у них разные «VIN» номера, а значит при проверке на посту «ГАИ» Вам сообщат что авто не Ваш. Ниже будет приведено дополненное объяснение этого явления.
Как Вы уже поняли метод "+alloc" — метод класса, а метод "-init" — метод экземпляра.
Приведу пример с реальными вещами.
Полная реализация метода включает в себя обычно 2 файла с расширениями *.h и *.m. Бывает и другое количество, в зависимости от того, что это за класс и какие цели он преследует.
В Вашей программе есть класс «Primus». В файле «Primus.h» объявлены методы:

Primus. h
//метод класса, можно применить только к классу "Primus"
+(Primus *) hotAsHell;
//метод экземпляра, инициализирует экземпляр класса "Primus"
-(id) initWithGas:(Gas *)gas temperature:(NSInteger)t;
//метод экземпляра, задаёт параметры пламени
-(void) doFireWithGas:(Gas *)gas;
//метод экземпляра, но его применение к текущему классу, вызывает экземпляр совсем другого объекта
-(MasterPoRemontu *)  masterPoRemontuWithServiceItems:(NSArray *)i 
                                    serviceSkills:(NSArray *)s;

Также в программе есть класс «Kitchen», где в файле «Kitchen.h», объявлены методы:

Kitchen.h
//метод класса, создаёт экземпляр объекта "Kitchen" и применяется непосредственно к объекту "Kitchen"
+(Kitchen *) kitchenFurniture:(NSArray *)furniture
                          otherDishes:(NSArray *)dish;
//метод экземпляра, применение его к кухне вызывает экземпляр другого объекта 
-(UkrainianBorsch *) borschWithIngredients:(NSarray)ingredients 
                                          casserolAndPan:(NSArray)pan;


В файлах «Primus.m» и «Kitchen.m» все объявленные методы должны быть реализованы, то есть описаны: как и что в каждом методе происходит.
Если вы собираетесь в каком-либо классе создавать экземпляр объекта другого класса, то в шапке нужно будет импортировать файл *.h создаваемого объекта, например в шапке файла Kitchen.h мы впишем


#import  "Primus.h"

То есть можно будет создать класс «Primus» внутри класса «Kitchen».
Так как мы импортировали заголовочный файл Primus.h в файл «Kitchen.h», то в классе «Kitchen» нам стали доступны те методы экземпляра класса «Primus», которые объявлены в файле Primus.h. Их можно будет применять либо по отношению к классу, либо по отношению к экземпляру класса «Primus».
В файле Primus.m обязательно нужно реализовать все методы, которые были объявлены в файле Primus.h, но также в нём можно реализовать любой метод не объявленный в заголовочном файле Primus.h. Этот метод будет доступен только изнутри класса и его нельзя будет вызвать в другом классе в программе. Так же можно создавать переменные внутри файла Primus.m — они так же не будут доступны для внешнего использования. О них просто не знают. Потому файл типа *.h и называется интерфейсом класса. Только то, что явно в нём указано — доступно извне тому классу, в котором прописано
 #import  "Primus.h" 
. Недоступность методов и переменных для других частей программы называется — инкапсуляция.

Для начала создадим экземпляр класса «Primus» внутри класса «Kitchen»

Primus *myPrimus = [Primus hotAsHell];

Как видите, мы посылаем метод c "+" не экземпляру класса «myPrimus», а непосредственно классу «Primus». Экземпляр класса «Primus» под названием «myPrimus» создан. В дальнейшем, методы из «Primus.h» со знаком "-" будут применяться к экземпляру класса — «myPrimus». И если захотим создать новый экземпляр класса «Primus», то можно будет опять воспользоваться методом "+hotAsHell". Также в классе «Primus» есть метод

-(id) initWithGas:(Gas *)gas temperature:(NSInteger)t;

Обычно все методы, название которых начинается с «init», делают то же самое что и метод «init» класса NSObject, только с расширенными возможностями. И в данном случае применение метода «init» в таком ракурсе

Primus *myPrimus = [[Primus alloc] initWithGas:Propan temperature:750];

создаст новый объект класса «Primus» с уникальными характеристиками.
В описанном только что примере был применён метод с аргументами. В данном, конкретном случае, он применяется для создания экземпляра класса «Primus» с чётко заданными характеристиками. По названию характеристик видно, что данный экземпляр класса «Primus» будет работать на газе типа «Propan» и при температуре 750 градусов.
Обратите внимание, что названия методов в Objective-C весьма осмысленны. Временами Вы будете поражены их названиями состоящими из 10 слов, но это способствует лучшему понимаю написанного. Грубо говоря код получается самодокументируемым. Что конечно же не исключает необходимости написания комментариев. Но если взять за правило создавать свои методы, название которых будет нести смысловую нагрузку и таким же образом относиться к созданию переменных, то количество комментариев в коде можно заметно снизить.
В отличие от некоторых других языков программирования в Objective-C методы не вызываются, а посылаются объектам. Таким образом, этот ЯП относится к «message oriented language», на русском языке звучит не так строго, поэтому перевод не привожу.
Также обратите внимание на то, что все методы которые после посылки объекту создают (возвращают) новый объект имеют в скобочках после "+" или "-" либо конкретное название объекта, экземпляр которого они будут возвращать, либо «id». То есть, по том, какой объект указан в скобочках сразу после знака "+" или "-", можно судить что мы получим после посылки этого метода. Что такое «id»? Это такой тип объекта, который может принимать форму (тип) любого другого объекта. Скажем это «слабый» тип объекта. Не в смысле того, что ему что-то «слабо». Если говорить общепринятыми понятиями, то это слабо типизированный объект. Наличие таких объектов в ЯП делает его слабо типизированным.
Также можно в этих самых скобочках обнаружить тип «void».

Что такое «void»?


Все говорят: «это когда метод ничего не возвращает». А зачем же он нужен? Применение метода этого типа всё же производит манипуляции с объектами, структурами и цифрами. Также объекты и скалярные величины, попавшие в него в качестве аргументов могут измениться либо вообще исчезнуть. Всё зависит от того, что реализовано внутри каждого конкретного метода. Дело в том, что «void» таки ничего не возвращает… нового. Как и было сказано, то что попавшие в него аргументы могут изменяться сами, либо влиять на другие объекты, манипуляция с которыми производится в методе с «void». Он может даже создавать новые объекты внутри себя, но наружу ничего не возвращается в явном виде. Обычно метод, который возвращает что-то, имеет ключевое слово «return» в самом конце. А метод типа «void», «return» в конце не имеет. Он может его иметь внутри своих собственных циклов или условий, чтобы иметь возможность прервать их в нужном месте. Но не в конце. Метод не создаёт ничего принципиально нового, он просто, работая с указателем на какой-то объект (число, массив, ячейка и т.д.), меняет данные по этому адресу или применяет их для расчёта других данных, которые присваивает другим объектам.

Что такое «self»?


Это слово очень часто применяется где попало. Сам факт его применения во всевозможных местах, вносит путаницу в стройные кладовые Ваших знаний.
Вернёмся к классам «Primus» и «Kitchen». Возьмём конкретный метод класса «Primus» и рассмотрим как будет выглядеть его реализация в файле «Primus.m». Например это метод.

-(void) doFireWithGas:(Gas *)gas;

может быть реализован таким образом:

-(void) doFireWithGas:(Gas *)gas {
        id myPrimus;
	if (!self){
		myPrimus = [self initWithGas:gas temperature:750];
	 }
        ...
        ...
        ...
}

В фигурных скобках происходит реализация или «имплиментация» метода. В данном случае, реализация метода читается так: если не myPrimus, то выполнить команду

[self initWithGas:propan temperature:750];

которая в свою очередь читается как: себе инициализировать с газом:gas температурой:750. То есть, метод посылается себе, а не какому-нибудь другому объекту. В данном случае «себе» — это классу «Primus», так как метод находится внутри класса «Primus». Его объявление — в файле Primus.h и реализация в файле Primus.m.
Также можно заметить, что метод ничего не возвращает «void». То есть получается каламбур: если примуса нет, то мы его создаём, но наружу он из метода не выходит. Зачем его тогда создавать? Допустим его функционал реализован так:

        id myPrimus;
        if (!self){
		myPrimus = [self initWithGas:gas temperature:750];
	 }
         MasterPoRemonty *master = [myPrimus masterPoRemontuWithServiceItems:someItemsArray 
                                                                                                                 serviceSkills:someSkillsAray];
}

И тут уже понятно что где-то внутри метода появляется мастер по ремонту примуса.
Почему был использован знак восклицания "!" в скобках после «if»? В программировании это — знак отрицания. То есть "!=" звучит как «не равно». Этот способ условия удобен своей лаконичностью. В данном примере можно было использовать другое по структуре условие, которое несло бы ту же самую функцию.

if (myPrimus == nil){}

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

if ((myPrimus == nil || yourPrimus == nil) && (mamaPrimus == nil || papaPrimus == nil)){}

то эта структура

if ((!myPrimus || !yourPrimus) && (!mamaPrimus || !papaPrimus)){}

будет в более выигрышной позиции, как по лаконичности так и по физическому размеру. Предыдущая конструкция — банально может просто не влезть в одну строку.
Рассмотрим как выглядела бы реализация этого метода в рамках класса «Kitchen». В файле «Kitchen.m» тот же метод будет реализован так:

-(void) doFireWithGas:(Gas *)gas {
        Primus *myPrimus;
	if (!myPrimus){
	 	Primus *myPrimus = [Primus initWithGas:gas temperature:750];
	}
        MasterPoRemonty *master =  [myPrimus masterPoRemontuWithServiceItems:someItemsArray 
                                                                                                                serviceSkills:someSkillsAray];
       ...
       ...
}

Видно, что здесь нужно указать, какой объект будет создаваться, как будет называться его экземпляр и потом непосредственно родительскому классу посылать метод.
То есть, хитрое слово «self», применяется внутри класса, как будто вы уже создали его экземпляр.

Primus *myPrimus;

и обращаетесь к экземпляру класса. Только фишка в том, что вы ничего не создавали, и обращаетесь не к экземпляру, а напрямую к классу. Для наглядности я создал объект

id myPrimus;

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

        if (!self){
        MasterPoRemonty *master =  [[self initWithGas:gas temperature:750] masterPoRemontuWithServiceItems:someItemsArray 
                                                                                                                                serviceSkills:someSkillsAray];
}

Для того, чтобы понять можно ли написать «self» или нельзя — просто представьте себе какой метод какому объекту Вы хотите послать и начните вводить слово «self», но не до конца, например «sel». Xcode предложит несколько вариантов, среди которых будет собственно «self». Слева от него будет описание — какой это класс. Если это именно тот класс, которому вы хотели послать нужный метод (класс внутри которого вы сейчас производите действие), значит используйте «self». Если Xcode указывает, что «self» это не тот объект, который Вам нужен — то используйте имя класса или экземпляра нужного Вам.
Также видно что здесь были задействованы аргументы. У каждого аргумента есть тип, который тоже описан в скобках, но после двоеточия. После скобок с типом аргумента идёт имя аргумента. Если в скобках присутствует знак "*" после типа аргумента, то это объект. Если знака "*" нет, то это скалярный тип, например «NSInteger», который по сути является простым «int». Также «NSInteger» может быть со знаком "*" для передаваемого аргумента. В таком случае мы будем передевавать не саму переменную, а указатель на неё. Тема Указателей выходит за рамки данной статьи. Имя аргумента, в данном методе

-(void) doFireWithGas:(Gas *)gas;

будет звучать как «gas». Вы обратили внимание, что в реализации не было использовано нормального названия газа? Например «Propan». Дело в том, что в аргументе «gas» как раз и передаётся нужное название газа. Откуда? Мы рассмотрели как реализовуется метод

-(void) doFireWithGas:(Gas *)gas;

В нём был задействован метод

-(id) initWithGas:(Gas *)gas temperature:(NSInteger)t;

В этом методе было 2 аргумента «gas» и «t». В посылке метода объекту, температуру мы указали, а тип газа просто продублировали от метода

-(void) doFireWithGas:(Gas *)gas;

То есть этот метод тоже где-то посылается. Рассмотрим поближе метод

(Primus *) hotAsHell;

Его реализация может выглядеть так

+(Primus *) hotAsHell{
	self = [super init];
	if (self){
		[self doFireWithGas:propanButan];
	}
	return self;
}

Обратите внимание на значение посылаемого аргумента «propanButan». Именно оно передаётся по всему классу, от метода к методу.
Также здесь видно что метод посылается объекту при определённых условиях.
Нужно знать что ни один Ваш метод сам по себе не может сработать. Ему нужен «пинок» снаружи. Нажатие кнопки, возникновения какого-либо события или его должен запустить метод протокола, который среда исполнения запускает в нужный ей момент. Данный метод «hotAsHell» посылается лишь после явного вызова, это не системный метод, он тоже где-то должен быть прописан либо чему-то назначен.
Теперь рассмотрим метод

-(id) initWithGas:(Gas *)gas temperature:(NSInteger)t;

Он состоит из типа возвращаемого объекта, типов аргументов, аргументов и селектора. Селектор это то что остаётся от метода когда от него отнять тип возвращаемого объекта, тип аргументов, и аргументы.

initWithGas:temperature:

именно с двоеточиями. Если аргументов нет, то двоеточий тоже нет, как в случае с «hotAsHell». Часто прийдётся сталкиваться с необходимостью употребить селектор. Теперь вы знаете что употреблять.
Немного выше было использовано понятие «super».

Что такое «super»?


Рассмотрим реализованный выше метод «hotAsHell». В нём есть такая конструкция

self = [super init];

Так принято инициализировать дочерний класс — через инициализацию родительского.
Предположим, что класс «Primus» — непосредственный потомок класса «NSObject». Именно тот класс, который стоит по иерархии сразу над тем классом, с которым мы работаем имеет право называться «super». Команда

[super init];

вызывает метод init экземпляра NSObject. То есть, автоматически создаётся экземпляр прародителя всех объектов — NSObject, с дефолтными (сугубо NSObject'овскими) параметрами. Посылка метода «init» NSObject'y возвращает «self» от NSObject'a. То есть непосредственно экземпляр самого NSObject'a. И этот экземпляр присваивается нашему объекту «Primus». Ведь «self» в данном случае — является именно «Primus’ом». И мы на данном этапе получаем экземпляр объекта «Primus», который ничем не отличается от NSObject’а. Индивидуальные черты ему придаёт наша посылка метода

[self doFireWithGas:propanButan];

а также остальные методы, реализованные в рамках данного класса.
Конструкция

if (self) {...}

это просто перестраховка на случай, если что-то пойдёт не так и дефолтный «Primus» не создастся. т.е. метод init класса родителя не выполнится. В таком случае обычно делают

...
else {...}

где пытаются исправить положение дел, но, к сожалению, это не тот случай и «else» нам уже ничем не поможет.
И здесь я хочу Вам напомнить о неинициализированном объекте из начала статьи. Именно при инициализации экземпляра объекта его суперклассом, происходит назначение места в памяти ему и передача указателя на это место в памяти. И когда проводим проверку

if (self = [super init])

то именно в этот самый момент «self» может получить неожиданный адрес в памяти. То есть, полученный указатель уже будет не «self». Суперкласс не возвратит нам что-то из ряда вон выходящее. Экземпляр объекта будет идентичен тому с которым мы собираемся работать. Но экземпляр именно этого объекта не будет тем экземпляром, который нам нужен. Это настолько редкая ошибка, что Вы можете её не наблюдать очень продолжительное время в своих приложениях. Просто в один прекрасный момент Ваше приложение может начать выдавать экзотические ошибки. Вследствие чего придётся потратить время на то, чтобы найти их причину. Выделенная память под Ваш объект не была задействована. Инициализироваться мог случайный указатель, не имеющий никакой связи в объектом, которому была выделена память методом «alloc». Вот тогда «self» не проходит проверку

if (self) {...}

В рассматриваемом методе инициализации. И всё потому что в каком-то месте программы при вызове этого объекта инициализация не была произведена должным образом.
Последнее, что мы должны сделать для завершения инициализации

return self;

Поскольку данный метод не «void», то он ожидает от нас, что в конце мы скажем ему что нужно возвратить. В данном случае будет возвращён объект типа «Primus»,  потому что «self» в рамках данного класса — именно «Primus». Также тип возвращаемого объекта нам говорит о том что ожидается именно «Primus».
В этих методах

+(Kitchen *) kitchenFurniture:(NSArray *)furniture
                          otherDishes:(NSArray *)dish;
-(UkrainianBorsch *) borschWithIngredients:(NSarray)ingredients 
                                          casserolAndPan:(NSArray)pan;

-(MasterPoRemontu *) serviceItems:(NSArray *)i 
                                    serviceSkills:(NSArray *)s;

тип возвращаемых объектов — «Kitchen», «UkrainianBorsch» и «MasterPoRemontu».
Таким образом, в методе «hotAsHell» мы говорим, что в конце выполнения данного метода желаем получить объект типа «Primus» с заданными параметрами.

Какими именно заданными параметрами?


Опишем такую реализацию метода

- (id) initWithGas:(Gas *)gas temperature:(NSInteger)t {
	...
		[self setGas:gas];
		[self setTemperature:t];
	...
}

Это значит, что при вызове этого метода

Primus *myPrimus = [[Primus alloc] initWithGas:metan temperature:500];

задаются буквально такие параметры

[self setGas:metan];
[self setTemperature:500];

Параметры задаются переменным, которые объявлены в файле *.h
Обратите внимание на конструкцию слова «setGas» или «setTemperature». Если есть переменная, например «variable», то задать ей нужное значение можно через префикс «set»:

setVariable

при этом первая буква переменной становится заглавной. Таким образом, мы выяснили что в файле Primus.h были объявлены переменные «gas» и «temperature». Но само наличие переменных не даёт нам возможности назначать их с помощью префикса «set»
Для получения такой возможности нужно объявить свойства для этих переменных. Допустим такие:

@property (nonatomic, strong) Gas          *gas;
@property (nonatomic)             NSInteger temperature;


а в файле имплементации нужно будет прописать

@synthesize gas, temperature;

и только после этого станет возможным присвоение им значения через «setGas» и «setTemperature». Эти префиксы называются сэттерами.
В любой удобный момент можно попросить объект «Primus» показать Вам эти значения, обращаясь к его экземпляру

[myPrimus gas]; 
[myPrimus temperature];

или

myPrimus.gas;
myPrimus.temperature;

например для команды

NSLog(@"Примус газовый работает на газе марки %@, рабочая температура %i", [myPrimus gas], myPrimus.temperature);

Эти методы называются гэттерами. В случае обращения за значениями к названию переменной, ничего дописывать не нужно.
Обратите внимание, что к свойству объекта можно обращаться как через конструкцию

[myPrimus gas];

так и через

myPrimus.gas;

Они выполняют аналогичные действия.
Есть одна хитрость при доступе к переменным объекта. Это можно производить не только через свойства, но и через доступ к ним по принципу ключ-значение «key-value coding». То есть можно обратиться к переменной

myPrimus.gas;

так

[myPrimus valueForKey:@"gas"];

Мы обратились за значением, которое находится в объекте «Primus» в ключевом слове «gas».
А такая конструкция

[myPrimus setGas:metan];

в контексте «key-value coding» будет выглядеть так

[myPrimus setValue:@metan forKey:@"gas"];

Вместо свойств использовать «key-value coding» — вполне нудное занятие. Но иметь понятие о нём необходимо, чтобы в случае, когда без этого не обойтись — воспользоваться.
Также не лишним будет указать тот факт, что с недавнего времени пременными в программировании на Objective-C уже не пользуются. Вместо них используют свойства. Свойства могут быть как доступными извне, так и инкапсулированными. Естественно, что для использования внутри методов можно и нужно использовать переменые. Но объявляние переменных в блоке «ivar», который находится в интерфейсе класса — это уже анахронизм.

runtime


Даже если этого ещё не произошло, есть вероятность того что вскоре Вы встретите в документации слово «runtime» и в пределах той же статьи слово «isa».
Понятие «runtime» можно охарактеризовать как «среда в которой происходит перевод Вашего кода на более низкоуровневый код». «runtime» написан непосредственно с использованием C и Ассемблера. Это ещё не перевод в машинный код, а приведение Вашего кода к языкам C и Ассемблеру. Ваш метод

[myPrimus initWithGas:gelium temperature:nil];

в «runtime» на С выглядит примерно как

objc_msgSend(myPrimus,@selector(initWithGas:temperature:),gelium,nil);

Этого для начинающего Cocoa программиста должно быть достаточно, чтобы понять: дальше лучше в дебри не лезть. Как только Вы перейдёте порог вхождения в клуб, то сами сможете отыскать нужную Вам информацию.

Пока же нас интересует что такое «isa».


Это переменная, которая объявлена непосредственно в NSObject’е. Единственная его переменная. Когда мы вызывали метод

[super init];

то создавался не только экземпляр класса NSobject, но и экземпляр этой переменной «isa», которая ссылается на NSObject. То есть, она конкретно говорит «runtime’у», что она принадлежит объекту NSObject. А значит, и работать с экземпляром Вашего, только что созданного объекта нужно как и с NSObject’ом. В «isa» записывается указатель на тот родительский объект, который нам нужно унаследовать. Допустим, Ваш объект — потомок NSArray или UITableView или CFDictionaryRef или любого другого объекта. В таком случае «isa» указывает на NSArray или UITableView или CFDictionaryRef или любой другой объект соответственно. Так что создание экземпляра любого объекта, создаёт и переменную класса — «isa», которая ссылается непосредственно на родительский класс, вследствие чего «runtime» знает как будет поступать с каждым экземпляром.
Эта информация на этапе обучения нужна не для чего-то конкретного, а в принципе, для более объёмного понимания философии программирования Objective-C.
В процессе чтения книг и различной документации, на глаза Вам не раз попадётся понятие «singleton». Как говорит один популярный интернет-мем: «Нельзя вот так сразу взять и понять, что такое „singleton“. 


Что такое синглтон?


Вообразим себе, что Вам нужно создать объект, который при каждом его вызове в любой точке приложения, возвращает один и тот же экземпляр. На самом деле, в процессе создания приложений, Вам действительно нужно будет такое создавать. Так почему же нельзя создать объект, несколько раз и присвоить ему те же данные через метод „initWithSomething:“ или с помощью сеттеров? Всё дело в работе с памятью и быстродействием, да и с собственно, меньшими затратами времени на написание кода. Памяти всегда мало, и даже когда на iPhone6 поставят 2Гб ОЗУ — её опять-таки будет мало. Создание одного экземпляра объекта, и последующее обращение к нему — экономит ресурсы устройства и ускоряет работу приложения. А ведь каждый хочет, чтобы его приложение было быстрым как „Bugatti Veyron“ и юзабельным как слово „хрен“.
Допустим что наш „Primus“ вполне может быть синглтоном. Тогда его метод

+(Primus *) hotAsHell;

при реализации будет выглядеть так

+(Primus*) hotAsHell{
	static Primus *myPrimus = nil;
	static dispatch_once_t predicate;
	dispatch_once (&predicate, ^{myPrimus = [[super alloc] init];});
	return myPrimus;
}

Рассмотрим что это значит позже. Пока что выясним, зачем „Primus“ делать синглтоном.
Допустим, это не вполне обычный „Primus“, а раритетный. На нём есть гравировка неизвестного мастера, он имеет крайне низкое потребление газа, а также у него есть специальная коробочка, в которой он идеально укладывается. А теперь задайте себе вопрос: „нужен ли Вам другой примус?“. Конечно же нет! Но при посылке метода

Primus *myPrimus = [[Primus alloc] initWitGas:metan temperature:500];

будет создан один „Primus“, а при посылке метода

Primus *myPrimus = [[Primus alloc] initWitGas:propan temperature:600];

будет создан абсолютно другой „Primus“. Без гравировки и коробочки.
Теперь вернёмся к тому, что написано в реализации метода „hotAsHell“.
Для начала нужно создать экземпляр объекта со свойством „static“, для того, чтобы перекрыть доступ к объекту извне. Затем присвоить ему „nil“, для того чтобы он не взял случайный адрес в памяти. Конструкция

static dispatch_once_t predicate;

создаёт предикат (условие) — »predicate" который тоже не будет виден извне. Условие заключается в том, что запрещается автоматический или динамический вызов блока, стоящего за предикатом. А вот строка

dispatch_once (&predicate, ^{myPrimus = [[super alloc] init];});

уже производит все необходимые действия для создания уникального экземпляра объекта. Конкретно «dispatch_once» означает что выражение в скобках после него гарантированно будет запущенно только один раз на протяжении всего жизненного цикла приложения. Также «dispatch_once» гарантирует потокобезопасность. То есть, при запуске приложения в многопоточном режиме эта функция не будет вызвана одновременно в нескольких потоках, и у ж точно не создаст нам ещё один «уникальный „Primus“». Также есть блок

^{myPrimus = [[super alloc] init];}

Это вроде маленькой функции или метода. Бывают также большие блоки. В данном блоке происходит инициализация объекта-родителя, после чего его значение присваивается потомку myPrimus.
Вместе вся строка

dispatch_once (&predicate, ^{myPrimus = [[super alloc] init];});

означает: один раз за весь жизненный цикл приложения будет инициализирован экземпляр объекта «Primus» под названием «myPrimus» со свойствами объекта-родителя и не будет возможности обратиться к этому блоку другим путём. Но о том что был создан «myPrimus» никто не узнает, потому что этот экземпляр объекта снаружи не виден. Все происходит в фоне и только единожды. И происходит благодаря GCD (Grand Central Dispatch). Рассказ о котором — отдельная тема.
И конечно же в конце мы возвращаем созданный синглтон

return myPrimus;

Синглтон — «Primus» у нас есть, теперь можно добавить ему свойства: коробочка — «Box», гравировка — «Etching», КПД — «Performance». И если объявить для них свойства, то можно будет извне менять эти переменные. Обшить коробочку, почистить гравировку, прочиповать наш «Primus» для повышения КПД. Но это останется наш старый добрый «Primus». Доступ извне к объекту «Primus» будет у тех классов, в шапке которых он объявлен. Но теперь, если делать так

Primus *myPrimus = [Primus hotAsHell]; 

а потом работать с «myPrimus» как с синглтоном, то ничего работать не будет. Все обращения к переменным синглтона должны происходить в следующей манере

Box *someBox = [Primus hotAsHell].Box;

так можно создать экземпляр «someBox» совсем другого класса коробок и ему присвоить значение, которое имеет коробка нашего синглтона или наоборот

[Primus hotAsHell].Box = someBox;

поменять коробку нашего «Primus».
Синглтон можно применять когда нужно вызвать NSLog с описанием свойств синглтона, а самого синглтона в данном классе нет в принципе. В таком случае его нужно просто объявить в шапке файла и вызвать один раз там где нужно. Синглтон можно и даже рекомендуют использовать в качестве глобальной переменной. Точнее его свойства будут глобальными переменными.
После усвоения основных принципов, можно будет начинать решать предлагаемые в учебниках задачи. И если с написанием своих методов в своих классах и посылкой метода объекту разобраться можно, то в дальнейшем, нужно будет использовать делегаты, протоколы и прочие MVC. Необходимо будет использовать документацию Apple и применять тысячи различных методов, заботливо созданных и описанных купертиновскими программистами.
И тут становится непонятно в принципе как и что работает. Если созданный Вами метод создаёт внутри себя массив, потом вносит в него объекты, потом запускает цикл и что-то в нём делает, а потом этот метод запускает другой Ваш метод, то вроде всё понятно. Но вот Вы открываете документацию по интересующему Вас объекту, а в нём 20 методов, которые могут делать весьма занятные вещи. Кроме того, есть объекты предки, методы которых этот объект тоже может принимать. Кроме того, в начале статьи я писал о протоколах, которым может соответствовать объект. Итого, методов может быть сотни. Какой из них нужно применять? Встречный вопрос: «для чего именно Вам нужен метод?». Правильно заданный вопрос — уже половина ответа. Когда Вы поймёте что хотите сделать, то в описании класса сможете найти интересующий Вас метод и применитьего поназначению. Если Вы хотите чтобы этот экземпляр объекта сделал что-то, присущее только классу от которого он произошёл, то в документации по этому классу нужно внимательно поискать метод, который производит необходимые Вам операции. Послать это метод объекту нужно так

[myPrimus doSomethingWith:Potato];

То есть, в документации Вы узнали, что этот метод берёт указанный аргумент «Potato» и делает с ним что-то, что в конечном итоге приводит Вас к цели если применить метод к объекту «myPrimus». Не нужно реализовывать этот метод, он реализован за Вас для прямого применения. Исключения составляют случаи, когда Вам нужно взять готовый класс из фрэймворка и субклассировать его так, чтобы при посылке стандартных методов его экземпляру, происходили нестандартные действия.

Протоколы


Есть также методы протоколов. Как я уже указывал в начале статьи, у них не может быть потомков. Они являются просто набором методов. Для того, чтобы применить метод протокола внутри себя, объект должен соответствовать этому протоколу. Это указывается в файле *.h

@interface Primus : UIViewController <UITableViewDataSource, UITableViewDelegate>

В данном случае, объект соответствует сразу двум протоколам «UITableViewDataSource» и «UITableViewDelegate».

И чем отличаются методы классов и протоколов?


Если зайти в описание этих протоколов, то там можно найти методы, которые объект может реализовать. Обратите внимание: методы протоколов Вы не посылаете своему объекту, а должны внутри них указать как должен отреагировать ваш объект, при обращении программы к этим методам. Некоторые методы в протоколах могут быть обязательными. И если вы решили что Ваш объект должен соответствовать протоколу, то обязательные методы нужно реализовать в первую очередь. Внутри любого метода протокола может быть любой метод класса. То есть, метод протокола — это контейнер, в котором находятся любые другие методы, собственно как и любой реализуемый Вами метод. Реализовать необходимый функционал внутри каждого метода протокола нужно исходя из потребностей. К примеру, нужно сделать так, чтобы Ваша формочка делала что-то, при определённых условиях. К примеру, меняла цвет, после того, как стала активной/неактивной. Идём в документацию Apple, смотрим какие протоколы реализуют нужные Вам методы. Затем ищем каким протоколам соответствует родительский класс формочки. Если протоколов, которые поддерживают нужный Вам функционал нет в стандартном наборе функций, то добавляем их в < > скобках. В описании этих протоколов ищем методы, которые реализуются после какого-либо события. Допустим

-(UIColor *) areaDidChangeColor:(CGRect)rect isActive:(BOOL)active;

который автоматически выполняется когда аргумент «active» принимает значение «YES». И меняет цвет в части экрана описанной в «rect»:

{
	if (isActive && myForm == rect){
		myColor = [UIColor redColor];
	}
	return myColor;
}

Методы протоколов задают параметры работы экземпляра класса, меняют функционал, передают значения. Например:

-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

возвращает количество строк в секции «section» для заданной таблицы «tableView».

На этом позволю себе закончить. Если статья поможет целевой аудитории, в которой несколько месяцев назад числился и я, то значит, что я думаю правильно и такие статьи нужны. Статьи не профессионалов, а людей, которые понимают что-то на таком уровне, который тяжело перешагнуть, не имея опоры или трамплина. Надеюсь эта статья кому-то будет трамплином или хотя бы табуреткой. Отсутствие хороших учителей, которые могут нормально что-то объяснить — фундаментальная проблема современности. У меня нет педагогического образования, но в статье изложено понятие в программировании в таком ключе, в каком лично мне было бы понятно.
Метки:
Поделиться публикацией
Похожие публикации
Комментарии 78
  • +1
    Большая статья, плюс за труд, но смущают некоторые моменты, например:

    NSObject *object = [NSObject alloc];
    

    выделил для него такой адрес в памяти

    0x000000000

    Так происходит потому что при инициализации, исполняемая среда, всем объектам задаёт значение «nil»...


    Возможно, вы имели в виду, что alloc инициализирует нулями все (кроме isa) переменные (члены) создаваемого экземляра?

    Иначе кто-то может подумать, что alloc возвращает нулевой адрес.

    Еще фрагмент:

    NSObject *object = [NSObject alloc];
    [object init];
    


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


    Скорее, здесь вероятность использования далее в программе неинициалированного объекта.

    В общем, было бы неплохо уточнить формулировки, у нас сейчас люди начинают изучение, боюсь, мне придется делать им дополнительные разъяснения после вашей статьи.
    • 0
      Уточнил
      • 0
        Скорее, здесь вероятность использования далее в программе неинициалированного объекта.

        Нет. В разных ситуациях объект может вернуть после вызова init абсолютно другой экземпляр. Например, посмотрите как устроен синглтон, станет сразу понятнее. Такие ситуации редки, но они тоже имеют место быть — например в задачах разнообразного кэширования объектов и так далее.
        • 0
          Вы правы, init может вернуть совсем другой объект, но я говорю о том же.

          Смотрите:

          NSObject *object = [NSObject alloc];
          [object init];
          
          [object doSomeAction]; // использование неинициализированного объекта
          

      • +17
        Какой противный синтаксис :(
        • +7
          Да, когда-то увидев это, я пришёл к твёрдому выводу, что под Мак писать не буду. %)
          • +2
            Под мак можно без проблем писать на С\С++ ;)
            • –1
              Я тоже так подумал, но писать пришлось. В итоге привык, полюбил и теперь даже смотреть на C++ после Objective-C мне противно.
              • –2
                Меня тоже синтаксис пугает. Но там не только это пугает, а то что лишний пробел в коде может привести к его невыполнению. Под мак можно спокойно писать программы на python или ruby. А для ios сойдет вот это или вот это
                • +2
                  лишний пробел в коде может привести к его невыполнению

                  А можно попросить вас немного развить эту мысль, а то не слишком понятно, что вы имели в виду.
                  • 0
                    тоже интересно.
                    • –2
                      * object
                      • +1
                        И всё же до сих пор не очень ясно.
                        • +1
                          NSObject *object = [NSObject alloc];
                          NSObject * object = [NSObject alloc];
                          

                          Две строки выше абсолютно одинаковы для компилятора, если вы это имеете в виду. Хотя, если вы пишете на C++, вам это должно быть известно.

                          Поясните, что вы имели в виду?
                          • –2
                            Два года назад xcode выдавал ошибку, когда я ставил пробел после звездочки. Я не сразу понял даже, где ошибка. А когда понял, психанул, потому что последний раз я сталкивался с этим в Turbo Pascal и тогда положил немало нервов.
                            Если в новом xcode это поправили, то это замечательные новости!
                            • +1
                              В новом xcode это не поправили, потому что этого никогда там не было.

                              Может, он и выдавал вам ошибку, но, боюсь, вы неправильно определили ее причину.

                              Тогда использовался gcc. Этот компилятор мог справиться с лишним пробелом и раньше 2010 года.
                              • –2
                                А почему, когда я пробел убрал, у меня все запустилось? Это была прога простейшего калькулятора. Как я мог неправильно определить причину в таком смешном деле?
                                • +1
                                  Лично я в это не верю, и вот почему: выражение «NSObject *foo» ничем по сути своей не отличается от конструкции «foo + bar», которую, очевидно ведь, можно записать и как «foo+bar», и как «foo +bar»

                                  Можно, конечно, установить gcc 4-5 летней давности (потому как «два года назад» этого не было точно) и проверить, но…
                                  Ах да, Xcode отображает все ошибки и предупреждения, выдаваемые компилятором, так что нужно было смотреть именно на эти сообщения, я думаю.
                                  • +2
                                    Ну, может и не совсем хороший пример привёл, но суть ясна: компилятору не важно, сколько вы используете пробелов/табов между операндами.
                                    • –1
                                      Ок, я встречался с интересным случаем в другом языке. Когда в программе была не критическая ошибка (о которой не сообщалось) и компилятор исполнял ее. При появлении критической ошибки, компилятор указывал на не критическую, а после ее исправления, показывал критическую. Но там был компилятор кривоватый, это я уже потом вычитал.
                                • +3
                                  Не выдавал, даже когда еще назывался Project Builder :)
                                  • –2
                                    И как вы тогда объясните, что я тут про этого говорю?
                                    • +2
                                      Никак, зачем мне объяснять чей-то бред?
                                      Всю жизнь пишу астерискус отдельно от имени, прямо сейчас делаю это в тигре (xcode 2.5) :)
                                      • 0
                                        Нет проблем, всей толпой меня тут убедили, что проблема во мне. Возвращаюсь к изучению ObjC. Но я вас тут всех запомнил :) Будут проблемы, буду спрашивать в личке. Не против?
                                        • 0
                                          спрашивайте :) буду рад проконсультировать ;)
                                          • 0
                                            Не против :)
                        • –2
                          Хватит уже обсирать другие языки программирования :) никакой он не противный :)
                          Просто каждому — свое :) у всех разные вкусы и предпочтения
                          • +1
                            Пусть не пишут, нам больше достанется. :-)
                          • 0
                            Как вариант, можно писать на Mono :)
                          • +25
                            Objective-C как первый язык программирования

                            Сочувствую. Сострадаю.
                            • +1
                              Objective-C как первый язык программирования — отличный выбор! Через 2-3 месяца изучения уже появится шанс устроиться джуниором. Сочувтсвовать надо начинающим С++ программистам!
                              • +3
                                Начинать надо не с Objective-C и не с С++, а с кристально чистого С.
                                • 0
                                  Не возражаю, но если задаться целью как можно быстрее войти в профессию, то сильно углубляться в С не стоит. Освоить синтаксис, немного порешать алгоритмических задачек, и переходить к Objective-C и Cocoa.
                                  • +2
                                    И остаться на всю жизнь посредственным программистом?
                                    Не углубляться в С и заниматься программированием — это как не учиться водить и участвовать в гонках. Понимание С ведет к пониманию того, как работает процессор, память и т.д. Знание того, как работает система позволяет писать быстрый и качественный код. А так же простой, как дверь — без куч ненужных интерфейсов, фабрик и прочего.
                                    Пример: у вас есть выбор — использовать лист или массив, скорость вставки нас не волнует — нам надо будет линейно искать элементы в одной из этих структур. Сложность поиска в обеих структурах — О(n). Аргументировано сделайте выбор структуры? Я задавал эту простую задачку «программистам» которые начинали со всяких ява\C# и они ничего не знают о памяти. Отвечали, что выберут массив, на вопрос почему ничего не могли ответить, видимо, потому что привычнее и эту структуру они выучили первой. Когда я акцентировал внимание на том, что сложность поиска в обеих структурах одинаковая, т.е. по сути ж никакой разницы в выборе нет. Они терялись.
                                    Ну и ответ, почему же я выберу массив — все элементы расположены друг за другом, это дружественно к кэшу. Я запрашиваю один элемент, а с памяти мне придут вместе с ним несколько других, расположенных за ним. В случае с листом, в зависимости от ужасности аллокатора памяти, я буду получать постоянные кэш миссы при доступе к следующему элементу массива. И на практике скорость поиска в массиве будет значительно быстрее — опять же, в зависимости от системы. На старых тачках с ддр1-2 латентность памяти ниже, чем у ддр3, следовательно кэш миссы будут «дешевле», а значения следующего элемента придет быстрее.
                                    Опять же, в зависимости от ужасности аллокатора памяти, реальный объем памяти выделенный под элементы листа может в разы превосходить по объему память для массива. Будет большая фрагментация памяти, что в больших системах приведет к понижению производительности всей системы в целом.
                                    • 0
                                      Можно сидеть дома и читать книжки по С, а можно пойти работать по специальности и получать ЗП. Я рекомендую второй вариант.
                                      PS: Приведенные в вашем примере знания, могут устареть раньше чем понадобятся. Начинающие программисты на ObjC тоже выберут массив, структуры данных «лист» в чистом виде в этом языке просто нет. А продвинутые программисты, если вообще будут заморачиваться на оптимизации, то выберут векторную обработку больших объемов данных.
                                      • 0
                                        Можно сидеть дома и читать книжки по С, а можно пойти работать по специальности и получать ЗП. Я рекомендую второй вариант.

                                        Можно сидеть дома и читать книжки по С, а можно пойти писать говнокод.
                                        • +1
                                          Для разработчика лучше получать опыт и делать ошибки, в которые более зрелые коллеги натыкают носом, и разжуют что и как. А работодателя тоже жалеть не надо, для него это осознанный выбор цена/качество.
                                          И вообще, что есть «говнокод»? Наверно вы имеете ввиду часть какого-то говнопроцесса, при котором говнокодеры делают говнокод в говнопректе для говноклиента за говнобаксы. А виноватым во всем этом сделаем джуниора, который недочитал книжку по С!
                                          • 0
                                            Для разработчика лучше получать опыт и делать ошибки, в которые более зрелые коллеги натыкают носом, и разжуют что и как.

                                            Именно для разработчика, а не для недоросли, которая не понимает, что происходит вообще. Такие люди тянут проект вниз, требуя (если вообще что-то пытаются понять) от коллег неоправданно много времени на растолковывание очевидных вещей и производя на свет говнокод, который ни в каком виде не годится ни для использования, ни для поддержки. Я жалею не работодателя, а разработчиков, обреченных нянчиться с такими кадрами, поскольку мне самому доводилось с этим иметь дело.

                                            Джуниор — это программист, знающий все свои инструменты и языки, но не обладающий достаточным опытом для самостоятельного принятия проектных или прочих сложных решений. Человек же, не владеющий фундаментальными знаниями, — это не джуниор, это быдлокодер.
                                            • +1
                                              То есть Вы отрицаете само существование джуниоров в том виде, в котором они сейчас присутствуют на рынке труда?
                                              И сами Вы небось даже не были джуниором? Родились с клавиатурой, программировали с детского садика, и даже в садике Вашему коду завидовал Макконнел.
                                              Если бы планка вхождения была на таком уровне — программировали бы только три человека: Керниган, Ритчи и Вы.
                                              • 0
                                                Таким джуниором, как описываете Вы, я не был. Я хорошо учился в университете. Огромная доля джуниоров, с которыми мне приходилось работать, такими не были тоже. А полуграмотные обезьянки в большинстве случаев такими же и обезьянками и остаются после трудоустройства. Есть, конечно, талантливые ребята, но таким прочитать K&R обычно не составляет труда.
                                      • 0
                                        Охохох, до недавнего времени в Obj-C не было ARC и с памятью работа была почти ручной :) Так что без понимания работы с памятью было никуда.
                                        А про то, как работают процессоры и глубже человек должен выучить в университете.
                                        По-моему, тем, кто начинает с Java или Objective-C, гораздо легче даются основы языков уровня пониже. Да и понимание приходит быстрее.
                                        Это же абстракция в чистом виде :)
                                        Люди сначала учат, что 2 * 2 = 4, и только потом учат, почему так :)
                                        • 0
                                          Я не предлагаю учить ассемблер. Я говорю о С, возьмите любую книгу по С для новичков — там сначала тоже все просто, как 2 * 2
                                          • +1
                                            Ну и я не говорю, что С учить не надо :) просто говорю, что начал с Objective-C и, по собственному опыту, считаю, что с него начинать удобно :)
                                        • 0
                                          Листы-то ведь бывают не только связные. И в C# и в Java в качестве стандартной реализации листа используют array list, в котором элементы хранятся в массиве и проблем с разбросом по памяти во время поиска не имеют.
                                          • 0
                                            Это был пример, хоть и плохой.
                                  • +1
                                    мой первый язык Objective-C. Я ни о чем не жалею. Что я делаю не так?
                                    • 0
                                      Какие языки знаете? На каких активно программируете?
                                      • 0
                                        знаю Java, Objective-C :)
                                        на обоих активно программирую :) Java — для университета, Objective-C — свои проекты :)
                                  • +3
                                    «Седельный тягач как первая машина. Учимся на практике.»

                                    Кстати, язык понятный. Если знаешь что угодно из C-семейства, то необычным кажется только синтаксис, но не сам смысл. Можно привыкнуть, короче.
                                    • +1
                                      А вот мы в университете понимали что такое ООП на Smalltalk и для меня obj-c теперь любимый и лучший :)
                                      • 0
                                        Спасибо за статью. Я начинал с делфи, сейчас изучаю Objective-C. Хочу сказать что синтаксис с кучей слов сначала был, ммм, странным да, но потом привыкаешь и оказалось что это очень удобно. Но как первый ЯП я бы его не выбрал :) Считаю, что сначала лучше выучить алгоритмы и ООП на чем-нибудь другом. Хотя, может я и не прав…
                                        • +2
                                          сейчас достаточно часто встречаю ссылки что в качестве первого языка лучше использовать CoffeeScript и Lua — из-за большого количества синтаксического сахара, и распространенности (CoffeeScript — Ruby и Web) и (Lua — игровая индустрия)
                                        • +3
                                          Какой-то излишне путанный способ объяснять принципы ООП. Создаётся впечатление, что автор сам еще до конца не понял что к чему.
                                          В данном случае «себе» — это классу «Primus»

                                          Точно классу а не экземпляру класса?

                                          Советую автору пописать чуть-чуть объектных моделей на Java — сразу всё станет очень просто и понятно.
                                          • +10
                                            TL;DR: Статья очень плохая, потому что внутри мешанина всех и вся. Кроме того,
                                            хотя знание С было заявлено как не нужное, а дальше идет куча ляпов из-за не
                                            знания С.

                                            Автору – вы учитесь и на правильном пути. копните глубже в фундаментальные вещи,
                                            это позволит вам лучше понять природу вещей. Ничего в программировании не
                                            происходит «автоматически». Докапывайтесь до причин, это весьма полезные знания.

                                            > Начало: метод класса и метод экземпляра

                                            Интересное «начало», учитывая какие темы рассматриваются дальше.

                                            > Класс для Objective-C, часто можно интерпретировать как объект и наоборот

                                            Очень сложное к пониманию предложение.

                                            > есть классы, которые нельзя назвать объектами. Они называются «протоколы»

                                            Протокол нельзя «создать», потому в данном случае сравнение не корректно.

                                            > Различие состоит в том, что «протокол» не может иметь подкласс или дочерний
                                            > объект

                                            То что вы не сталкивались с иерархией в протоколах, не значит, чо ее нет ;-)

                                            И да, «дочернего объекта» у протокола может и не быть. Но можно «привести» тип
                                            объекта к протоколу.

                                            > Вот те методы, которые со знаком "-" мы активно применяем как в отношении
                                            > объектов — экземпляров класса, так и отношении самого класса

                                            Нет, методы с "-" нельзя применять к классу.

                                            > Любой другой объект в Objective-C по определению — потомок NSObject

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

                                            > NSResponder’а тоже могут быть дочерние объекты

                                            Наверно дочерние классы?

                                            > выделяет для него место в ОЗУ

                                            Выделяет место в ОЗУ как раз таки +alloc, а не -init.

                                            > выделил для него такой адрес в памяти
                                            > 0x000000000

                                            У вас только что зафейлился malloc, сопротивление бесполезно. В качестве примера
                                            указателя лучше использовать что-то с живыми цифрами.

                                            > Вы будете удивлены тем, чему равняется Ваше «i»

                                            Не буду удивлен. А почему? А потому что я знаю как работает стек. Знать про стек
                                            в языках основанных на С – полезно. Иногда полезнее, чем уметь создавать
                                            сабклассы табличек с кнопочками в ячейках.

                                            > Также будет создан экземпляр объекта «isa»

                                            Нет. isa – это указатель на структуру класса. Она уже создана рантаймом (еще во
                                            время линковки).

                                            > То есть теоретически объект уже есть после применения метода «alloc»

                                            Он практически «есть». Но «существовать» – не значит «быть готовым к
                                            использованию».

                                            > потому что он вроде как есть, но в принципе он равен «nil»

                                            Он не равен nil, я вам точно говорю.

                                            > В файле «Primus.h» объявлены методы:

                                            Не совсем понятно куда пропали ± ил примера. Он не нагляден.

                                            > В отличие от некоторых других языков программирования в Objective-C методы не
                                            > вызываются, а посылаются объектам

                                            Это предложение, в контексте параграфа, лишено смысла, так как не объясняет, в
                                            чем, собственно, разница.

                                            > Скажем это «слабый» тип объекта

                                            Не стоит так говорить, потому что будет сложно ответить на вопрос, что такое
                                            "__weak id".

                                            > Что такое «void»?

                                            void – это тип данных, который фактически обозначает «тут ничего нет». Нельзя
                                            объявить переменную «void» – потому что в ней ничего не будет. Но можно объявить
                                            указатель «void *» – потому что переменная хранит адрес, а вот адрес чего, ее
                                            уже как-то не так сильно волнует. Фактически проблема будет только если этот
                                            указатель разименовать.

                                            Методы возвращают «void» когда они, собственно, не возвращают ничего. В паскале
                                            для них есть отдельный термин – процедура, но обычно ЯП на такое не
                                            размениваются.

                                            > Но не в конце

                                            return можно писать и в конце (хотя в этом и мало смысла, да). Но метод, который
                                            возвращает void не может содержвать в себе код «return XXX;», потому что все что
                                            программист напишет в XXX – осязаемо и имеет смысл.

                                            > Что такое «self»?

                                            Очевидно это указатель на текущий объект в контексте. Или на класс (т.к. класс
                                            тоже объект). Но вы не рассмотрели указатели, потому что они слишком
                                            фундаментальны и принадлежат миру С, так что приходится выкручиваться.

                                            А еще стоило бы сказать про канонические аргументы self и _cmd в любом IMP и все
                                            стало бы проще.

                                            > if (!myPrimus){

                                            в контексте инициализатора в Objective-C эта проверка лишена смысла. Проверяют,
                                            обычно, что [super init] не вернул nil (эта ситуация на самом деле крайне редка
                                            и сейчас больше дань традиции). В вашем коде проверяется что +alloc не вернул
                                            nil, но если он вернул nil, у вас есть намного более существенные проблемы, о
                                            которых надо волноваться в данный момент.

                                            > Для того, чтобы понять можно ли написать «self» или нельзя

                                            В лобом методе можно писать self.

                                            > casserolAndPan:(NSArray)pan

                                            NSArray * же.

                                            > Если есть переменная, например «variable», то задать ей нужное значение можно
                                            > через префикс «set»

                                            Задать значение переменной можно через оператор "->"

                                            self->temperature = 500;

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

                                            > Это можно производить не только через свойства, но и через доступ к ним по
                                            > принципу ключ-значение

                                            KVC в статье, где рассказывается про базовое ООП, как-то несколько лишнее.

                                            > Понятие «runtime» можно охарактеризовать

                                            Нет. Runtime – это дословно «среда исполнения». Это тот код, который выполняет
                                            все то, что в вашем коде остается «за кадром». Этот код, фактически, выполняет
                                            все те «магические» штуки. И не важно, на чем он написан. В случае с
                                            Objective-C, там есть и C++, а у C++ есть свой рантайм.

                                            > [myPrimus initWithGas:gelium temperature:nil];

                                            второй аргумент – это NSInteger, пишите типы корректно.

                                            > Единственная его переменная

                                            NSObject – он не такой простой.

                                            > Затем присвоить ему „nil“, для того чтобы он не взял случайный адрес в памяти. Конструкция
                                            > static dispatch_once_t predicate;

                                            Значит объекту nil надо присваивать, а предикату – нет? А как он тогда работает?
                                            И снова надо копать в С, и в static, и в то, что статические переменные при
                                            запуске приложения будут обнулены.

                                            > [super allocWithZone:nil]

                                            Ну зачем вы еще +allocWithZone: вытащили, +alloc мало?

                                            > опять же присваивая ему «nil» в качестве адреса размещения

                                            Откуда у вас эти идеи берутся? Наверно, от того что вы продолжаете игнорировать
                                            наследие С? PAGEZERO например, сегмент, который применяется для первой страницы
                                            в виртуальном адресном пространстве что бы ловить «бажные» нулевые указатели.

                                            > Методы протоколов Вы не посылаете своему объекту

                                            Что мешает вам это делать? Это вообще нормальное использование протоколов.

                                            > Внутри любого метода протокола может быть любой метод класса

                                            О чем это?

                                            > То есть, метод протокола — это контейнер, в котором находятся любые другие
                                            > методы, собственно как и любой реализуемый Вами метод

                                            Я даже не могу прокомментировать, просто не понятно, что происходит.

                                            > К примеру, нужно сделать так, чтобы Ваша формочка делала что-то, при
                                            > определённых условиях

                                            Формочка – это наследие делфи? Раз уж было упомянуто MVC, то и остальную
                                            терминологию стоило бы причесать.
                                            • –1
                                              Ох. Меня тоже все это смущает. Очень неточные формулировки и странные (иногда) выводы.
                                              • 0
                                                > return можно писать и в конце (хотя в этом и мало смысла, да). Но метод, который
                                                > возвращает void не может содержвать в себе код «return XXX;», потому что все что
                                                > программист напишет в XXX – осязаемо и имеет смысл.
                                                void foo() { return void(); }
                                                :)
                                              • –5
                                                Замечания конечно вполне уместные. Но хотел бы я посмотреть на Ваши формулировки озвученных в статье вопросов, если бы вы от силы 2 месяца программировали. А до этого были сисадмином — эникейщиком.
                                                • +5
                                                  Я повторяю еще раз – плюс за старания. Минус – за содержимое. Старайтесь еще!
                                                  • +5
                                                    Если бы лично я программировал от силы 2 месяца, а до этого был сисадмином-эникейщиком, я бы не стал писать такую статью.

                                                    По теме: farcaller написал очень правильные, но, вероятно, вам пока не понятные вещи. Я рекомендую вам на данном этапе просто сесть и прочитать от корки до корки (с перерывами на практику, разумеется) какую-нибудь толковую книгу по языку, например, «Objective-C 2.0» Стивена Кочана.
                                                    И да, на Си тоже следует обратить чуть больше внимания, чем кажется на первый взгляд: код на Objective-C это не только квадратные скобки, это и есть самый настоящий Си.
                                                    • +6
                                                      Но хотел бы я посмотреть на Ваши формулировки озвученных в статье вопросов, если бы вы от силы 2 месяца программировали.

                                                      И в итоге описание получается в духе

                                                      Это может еще больше запутать начинающего программиста.
                                                      • 0
                                                        Но писать заведомо неправильные вещи тоже не очень хорошо
                                                    • –9
                                                      Статью не читал, но осуждаю.

                                                      > Objective-C как первый язык программирования

                                                      Вы с ума сошли начинать образование будущего (хотелось бы верить) профессионала с ООП.
                                                      • +1
                                                        ООП-то как раз там самое каноничное ;)
                                                        • 0
                                                          Я с Java начал (который писал народ ушедший с Objective-C кстати) и что? Потом тот же C и C++ (Objective-C уже два года как знаю, пишу игру) выучил и вполне нормально живу и дышу. Хотите поспорить давайте в личку.
                                                        • 0
                                                          Нетипобезопасный язык программирования, приучающий ложить на типизацию большой прибор как первый язык? Отказать.
                                                          • +1
                                                            Этот язык не приучает класть на типизацию и опасен не более, чем C или C++ при работе с указателями.
                                                            • –2
                                                              А как же волшебный тип id? В C++ хотя бы кастить надо к нужному типу, чтобы метод вызвать, причем кастинг проверяемый (если dynamic_cast).

                                                              Ну и отсутствие какой-либо формы параметрического полиморфизма делает все коллекции нетипобезопасными.
                                                              • –1
                                                                Объясню свое недовольство по поводу id. Часто вижу такое, что разработчики делают property/ivar/аргумент типа id с мыслью «а, потом придумаю, что сюда передавать, все равно ведь можно вызвать любой метод будет и проверить класс через isKindOfClass». Нафига думать и проектировать архитектуру, точно зная что куда передается, действительно, ведь есть же id!
                                                                • +3
                                                                  Какие-то говнокодеры вам попадаются, ей-бѳгу. Нужно очень любить боль, чтобы «везде использовать id, а потом на каждом шагу делать проверки isKindOfClass:». Тут не язык виноват, поверьте.
                                                                  • 0
                                                                    ну, наличие duck typing очень к этому располагает
                                                                    • +4
                                                                      Может быть я какой-то особенный Obj-C программист, но мне даже в голову не приходило, что id можно использовать там, где тип объекта однозначен. Хотя Objective-C и был по сути моим первым языком программирования (наряду с Си, впрочем).

                                                                      «Наличие в доме ножей располагает к убийству с последующим снятием скальпа». Ну правда :)
                                                              • 0
                                                                Наличие id не означает, что Objective-C поощряет его использовать вместо конкретных типов.
                                                                • 0
                                                                  Отсутствие типобезопасных коллекций тоже не поощряет пихание разнородных элементов в NSArray, однако лишает разработчика ряда compile time проверок.
                                                                  • 0
                                                                    При использовании id разработчик лишается compile time проверок, при использовании конкретных типов — не лишается. Так что же рациональнее использовать разработчику?
                                                          • +2
                                                            В общем, очень весело получилось.
                                                            Зашел в тему, сказал, что не стоит унижать другие языки программирования, что у каждого свой вкус; высказал свое мнение, не навязывая его никому из присутствующих.
                                                            Карма поползла вниз, комментарии заминусованы.
                                                            Хабр в собственном соку…
                                                            • –1
                                                              Не бойтесь, унижатели языков вроде меня (это мой комментарий сверху самый заплюсованный в топике) — тоже по карме словили. :)
                                                            • 0
                                                              Если Вы, никогда не программировали, то книга Кернигана и Ритчи (в любом издании), Вам явно будет не по силам.

                                                              Если вам явно не по силам читать K&R, то вам явно еще рано претендовать на должность разработчика ПО.

                                                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.