Пользователь
0,0
рейтинг
29 октября 2010 в 17:40

Разработка → Objective-C с нуля

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

Банальная теория возникновения ООП


Проблема повторного использования написанного кода и его переносимость постоянно заставляет программистов искать все новые пути его упорядочивания, структуризации и абстрагирования. Для решения этих проблем создаются новые парадигмы программирования, шаблоны проектирования, новые языки, компиляторы и стандартные библиотеки к ним, программные платформы и фреймворки. Так образовались парадигма подпрограмм (процедур), реализуемая при помощи процессорных команд CALL\RET и стека (по сути, перенос потока выполнения по адресу произвольной, а не следующей за текущей команды, с последующим возвратом). Затем, парадигма модулей (каждый файл – отдельная единица трансляции), породившая двухэтапную трансляцию: компиляция модулей, а затем их компоновка (статическая или динамическая) в исполняемый модуль.

В следствии увеличения объема кода в проектах и сложностей его поддержки, с 1960х начинает образовываться новая, объектно-ориентированная парадигма программирования, разбившая программы на еще более мелкие составляющие – типы данных. Ее суть заключается во взаимодействии сущностей (объектов) посредством посылки друг другу сообщений. Каждый объект является переменной определенного программистом типа данных (так называемого класса). Определение такого специального пользовательского типа данных (класса) заключается в двух вещах: определении набора данных (инвариантов, членов) и набора подпрограмм (методов), которые будут их обслуживать.


Класс обычно оформляется как определенный программистом тип, основанный на встроенных (языковых) типах данных и\или других классах. Для языка С, не поддерживающего объектно-ориентированную парадигму, это может быть структура (struct). А набор подпрограмм реализуется как обычные функции, обязательно принимающие как минимум один параметр — указатель на набор данных, подлежащих обработке.

Основным преимуществом объектно-ориентированного подхода стала возможность создавать новые классы на основе уже написанных (добавлять инварианты и методы, переопределять методы, использовать определенные в базовом классе методы как свои), названное наследованием.


Набор методов представляет собой интерфейс для взаимодействия с инвариантами. Невозможность непосредственной модификации данных класса (без задействования его интерфейса) отражает принцип инкапсуляции. На рисунке показан класс и его объекты. Имеется инвариант x типа float и к нему интерфейс (метод) doubleX, возвращающий значение инварианта.

Бывает, что необходимо послать сообщение объекту, который на него определенно отвечает (т.е. вызвать для объекта класса такой метод, который он реализовал), но, по ситуации, конкретный класс этого объекта неизвестен. Например, каждому элементу списка указателей на объекты класса Auto нужно послать сообщение Move, а известно что в списке находятся указатели на объекты не только класса Auto, но также и указатели на производные (наследованные) классы Ford и Subaru. Это возможно сделать только благодаря принципу полиморфизма, заключающегося в том, что при посылке определенного сообщения объекту из некой иерархии классов, в которой все объекты способны принять такое сообщение, этот объект реагирует на него соответственно своему, а не базовому для данной иерархии классу.

Первым языком с поддержкой объектно-ориентированного подхода стал Simula67. Затем появился Smalltalk. А в 80х начал оформляться С++ — основной язык современного системного программирования. Его расширение и усовершенствование в 90х породило ряд парадигм и шаблонов проектирования, и оказало необратимое влияние на современное видение объектно-ориентированного подхода, в том числе, и на язык Objective-C.

Чуть-чуть истории


Objective-C возник в 80-x как модификация С в сторону Smalltalk. Причем модификация эта состояла в добавлении новых синтаксических конструкций и специальном препроцессоре для них (который, проходя по коду преобразовывал их в обычные вызовы функций С), а также библиотеке времени выполнения (эти вызовы обрабатывающей). Таким образом, изначально Objective-C воспринимался как надстройка над C. В каком-то смысле это так и до сих пор: можно написать программу на чистом С, а после добавить к ней немного конструкций из Objective-C (при необходимости), или же наоборот, свободно пользоваться С в программах на Objective-C. Кроме того, все это касается и программ на С++. В 1988 NeXT (а в последствии Apple) лицензировала Objective-C и написала для него компилятор и стандартную библиотеку (по сути SDK). В 1992 к усовершенствованию языка и компилятора подключились разработчики проекта GNU в рамках проекта OpenStep. С тех пор GCC поддерживает Objective-C. После покупки NeXT, Apple взяля их SDK (компилятор, библиотеки, IDE) за основу для своих дальнейших разработок. IDE для кода назвали Xcode, а для GUI – Interface Builder. Фреймворк Cocoa для GUI разработок (и не только) на сегодня является наиболее значимой средой разработки программ на Objective-C.

Особенности Objective-C


Файлы модулей на Objective-C имеют расширение “.m” (если использовалась смесь С++ и Objective-С, то расширение “.mm”). Заголовочные файлы – “.h”. Все, создаваемые в Objective-С объекты классов должны размещатся в динамической памяти. Поэтому особое значение приобретает тип id, который является указателем на объект любого класса (по сути void *). Нулевой указатель именуется константой nil. Таким образом, указатель на любой класс можно привести к типу id. Возникает проблема: как узнать к какому классу относится объект, скрывающийся под id? Это делается благодаря инварианту isa, который присутствует в любом объекте класса, унаследовавшего специальный базовый клас NSObject (приставка NS обозначает NeXT Step). Инвариант isa относится к зарезервированному типу Class. Объект такого типа позволяет узнавать имена своего и базового класса, набор инвариантов класса, а также прототипы всех методов, которые реализовал этот объект и их адреса (посредством локального списка селекторов). Все зарезервированные слова Objective-C, отличающиеся от зарезервированных слов языка С, начинаются с символа @ (например @protocol, selector, interface). Обычно имена инвариантов классов с ограниченной областью видимости (@private, protected) начинаются с символа подчеркивания. Для строк в Cocoa имеется очень удобный класс NSString. Строковая константа такого класса записывается как @”Hello world”, а не как обычная для С строковая константа “Hello world”. Тип BOOL (по сути unsigned char) может принимать константные значения YES и NO. Все особые для Objective-C зарезервированные слова (которые отличаются от языка С и находятся в заголовочном файле objc/objc.h) приведены ниже:
  • interface Начинает объявление класса или категории (категория – расширение класса дополнительными методами без наследования)
  • @implementation Начинает определение класса или категории
  • @protocol Начинает объявление протокола (аналог класса С++, состоящего из чисто виртуальных функций)
  • end Завершает объявление\определение любого класса, категории или протокола
  • @private Ограничивает область видимости инвариантов класса методами класса (аналогично С++)
  • protected Стоит по умолчанию. Ограничивает область видимости инвариантов класса методами класса и методами производных классов (аналогично С++)
  • @public Удаляет ограничения на облать видимости (аналогично С++)
  • try Определяет блок с возможной генерацией исключений (аналогично С++)
  • @throw Генерирует объект-исключение (аналогично С++)
  • catch () Обрабатывает исключение, сгенерированное в предшествующем блоке try (аналогично С++)
  • finally Определяет блок после блока try, в который предается куправление независимо от того, было или нет сгенерировано исключение
  • @class Сокращенная форма объявления класса (только имя (аналогично С++))
  • selector(method_name) Возвращает скомпилированный селектор для имени метода method_name
  • @protocol(protocol_name) Ворзвращает экземпляр класса-протокола с именем protocol_name
  • @encode(type_spec) Инициализирует строку символов, которая будет использована для шифрования данных типа type_spec
  • @synchronized() Определяет блок кода, выполняющегося только одной нитью в любой определенный момент времени

Обмен сообщениями


Чтобы заставить объект выполнить какой-нибудь метод нужно послать ему сообщение, именуемое так же, как и требуемый метод. Такое сообщение называется селектор метода. Синтаксис посылки таков:

[receiver method];


В сообщении можно передавать параметры для вызываемого метода:

[receiver method: 20.0 : 30.0];

Перед каждым параметром необходимо ставить двоеточие. Сколько двоеточий – столько и параметров. Имя метода может продолжаться после каждого такого двоеточия-параметра:

[receiver methodWithFirstArgument: 10 andSecondArgument: 20];

Методы с неограниченным количством аргументов вызываюся следующим синтаксисом:

[receiver undefinedNumberParameters: one, two, three, four, five, six, seven];

Посылка сообщения, как и любая функция C, возвращает определенное (может void) значение:

BOOL booleanValue;
booleanValue = [reveiver method];

При посылке сообщения nil оно просто пропадает. При посылке сообщения объекту, который принадлежит классу, не реализовавшему заказанный метод, возникает исключение, которое, будучи не перехваченным, приводит всю программу к незапланированному завершению. Для проверки, отвечает ли данный объект на кокое-либо сообщение можно использовать следующий шаблон кода:

if ([anObject respondsToSelector: @selector(myMethodWith2Argumets::)])
{
//можно вызывать
[anObject myMethodWith2Argumetns: @”first” : @”second”];
}
else
{
//ни в коем случае не вызывать
}


Как работает передача сообщений


Посылка сообщения транслируется в С-функцию с прототипом:

id objc_msgSend(id receiver, SEL method, ...);

Тип SEL, по сути, определен как char const *, но лучше воспринимать его как int, поскольку во время выполнения все селекторы индексируются целыми значениями согласно глобальной таблице селекторов.


Пользуясь инвариантом isa объекта receiver (при использовании фреймворка Foundation, базового для Cocoa, все классы должны наследовать класс NSObject, поэтому наличие isa неизбежно), эта функция просматривает локальный список селекторов класса с целью определить, отвечает ли объект данного класса на сообщение method. Если такой селектор находится, то управление передается соответствующему методу класса, которому передается id объекта (указатель на его инварианты) и указанные после селектора параметры функции objc_msgSend(). Значение, возвращенное методом, отдается как результат посылки сообщения. Если у объекта-приемника данный селектор отсутствует, функции objc_msgSend() просматривает список селекторов его базового класса.


При такой схеме вызов, например:

[receiver аddObject: otherObject];

Транслируется в:

objc_msgSend(receiver, 12, otherObject);

Так как в глобальной таблице селекторов 12 соответствует строке “addObject:”. Далее функция objc_msgSend() выполняет поиск по списку селекторов объекта receiver и, найдя его (пусть это объект класса NSArray, который реализовал метод с селектором 12), производит вызов типа:

addObject(receiver, otherObject);


Объявление метода


Интересно отметить, что прототип метода addObject из предыдущего раздела в объявлении класса выглядел так:

- (void)addObject: (id)otherObject;

То есть принимал всего один параметр. Но, исходя из принципа объектно-ориентированной парадигмы, что методы – это подпрограммы, обрабатывающие определенные наборы данных, методу необходимо передавать адресс данных, подлежащих обработке. Поэтому такой параметр передается во всякий метод класса неявно. Компилятору об этом дополнительном параметре дает понять минус ("-"), стоящий первым в прототипе метода. Такой метод (с минусом впереди) называется методом объекта (или экземпляра), т.к. может быть вызван только для объекта какого-нибудь класса. В теле метода этот указатель на экземпляр данных (или адрес объекта, которому послали сообщение) доступен посредством зарезервированного слова self (аналог this в С++), а указатель на экземпляр базового класса – через зарезервированное слово super. Кроме того, в метод объекта также передается неявный параметр _cmd – селектор этого метода из глобальной таблицы селекторов. С точки зрения программиста С++ все методы объектов в Objective-C как-будто объявлены с ключевым словом virtual, и всегда следуют динамическому полиморфизму.

Если в начале прототипа метода поставить знак плюс (“+”), то такой метод будет считаться методом класса, и, естественно, не будет принимать неявный параметр self (это аналогично объявлению static-метода в С++). А без инварианта isa объекта, на который указывает self, указатель super работать, конечно, тоже не будет.
Таким образом, прототип любого метода объявляется так:

-|+ (<тип возвращаемого значения>) основнаяЧастьИмениМетода
[ : (<тип первого параметра>)имяПервогоФормальногоПараметра
[ [дополнительнаяЧастьИмениМетода] : (<тип второго параметра>)имяВторогоФормальногоПараметра]
… ]

Например:

+ (Class)class;
+ (id)alloc;
- (id)init;
- (void)addObject: (id)anObject;
+ (NSString *)stringWithCString: (const char*)aCString usingUncoding: (enum NSStringEncoding)encoding;
- (NSString *)initStringWithFormat: (NSString *)format, …;

Если метод возвращает некий объект (тип id) или класс (тип Class), можно воспользоваться вложенным синтаксисом вызова:

[myLabel setText: [[NSString stringWithString: @”Hello”] stringByAppendingString: @” world”]];

Здесь объекту класса UILabel из фреймворка UIKit устанавливается значение инварианта text равное строке @”Hello world”. Эта строка, в свою очередь, образована конкатенацией строк @”Hello” и @” world”. Первая является результатом посылке сообщения stringWithString классу NSString с параметром-константой @”Hello”. Такой вызов возвращает объект класса NSString, инициализированный строкой-параметром. Затем этому объекту посылается сообщение stringByAppendingString с параметром @” world”. Результат посылки этого сообщения и есть объект класса NSString, содержащий конкатенацию значения объекта-приемника и строкового аргумента. Этот объект и попадает как параметр в сообщение setText: объекта myLabel.

Объявление класса


Объявим простой класс комплексного числа в файле Complex.h:

#import <Foundation/Foundation.h> //для NSObject и строк NSString

@interface Complex : NSObject
{
double _re; //инвариант для действительной части
double _im; //инвариант для мнимой части
NSString *_format; //строка формата для метода description
}
- (id)initWithRe: (double)re andIm: (double)im; //специализированный конструктор
+ (Complex *)complexWithRe: (double)re andIm: (double)im; //метод класса для одноэтапного создания объекта
- (Complex *)add: (Complex *)other; //метод для сложения
- (Complex *)sub: (Complex *)other; //метод для вычетания
- (NSString *)format; //метод доступа к _format
- (void)setFormat: (NSString *)format; //метод установки _format
- (double)re; //остальные методы доступа к действительной и мнимой частям
- (void)setRe: (double)re;
- (double)im;
- (void)setIm: (double)im;
@end

Как видим, все объявление заключено в ключевые слова interface и end. Первым делом объявляются инварианты (в фигурных скобках). Вне фигурных скобок объявляются методы. Метод description отсутствует в объявлении класса не случайно. Дело в том, что он, как и метод dealloc и init, присутствует в определении класса. При посылке объекту класса Complex сообщения description будет рассмотрен его локальный список селекторов, куда, после компиляции, попадут селекторы всех методов, реализованных классом этого объекта, даже не объявленные в интерфейсной части. То есть init, description и dealloc будут вызывать абсолютно корректно.

Создание объектов


В связи с тем, что все объекты распределяютя в динамической памяти, cоздание объекта приходится проводить в два этапа: 1) выделении памяти (сообщение alloc) и 2) инициализация инвариантов (конструкторы класса).

MyClass *myObject = [[MyClass alloc] init];  //метод класса MyClass alloc выделяет участок памяти нужного размера и возвращает указатель на него, метод объекта init инициализирует инварианты объекта myObject

После создания объекта им можно смело пользоваться:

NSMutableArray *array = [[NSMutableArray alloc] init]; //создаем изменяемый массив
MyClass *myObject = [[MyClass alloc] init]; //наш объект
[myObject myMethod]; //посылка некоторого сообщения
[array addObject: myObject]; //помещаем объект в массив
MyClass *otherObject = [array getLastObject:]; //достаем его из массива, указываем на него другим указателем
[otherObject myOtherMethod: YES]; //посылаем ему другое сообщение с аргументом типа BOOL

Некоторые классы обладают методом для быстрого (в один этап) создания собственных экземпляров. Такие методы являются методами класса, возвращают указатель на объект своего класса и их имя обычно начинается с названия самого класса. Например метод:

+ (NSString *)stringWithCString: (char const *)string encoding: (NSStringEncoding)encoding;

Возвращает уже готовую строку, инициализированную соответствующей сторокой с завершающим нулем, без вызовов alloc и init:

NSString *myString = [NSString stringWithCString: “Bla-bla-bla” encoding: NSASCIIStringEncoding];


Время жизни объекта


Как только указатель на объект выходит за свою область видимости, память, выделенная под него, безвозвратно теряется (если, конечно, это был последний указатель на тот объект) и происходит утечка. Дабы избежать таких нежелательных последствий в Objective-C поддерживается парадигма подсчета ссылок на ресурсы. Таким образом, у каждого объекта есть целочисленный счетчик, который показывает количество ссылающихся на него указателей. По достижению этим счетчиком нуля, память, выделенная для данного объекта, возвращается системе. После вызова метода класса alloc, этот счетчик равен единице. Чтобы увеличить его значение необходимо послать объекту сообщение retain, а чтобы уменьшить – release. Все эти методу реализует NSObject, который любой наш класс непременно наследует. Интересно отметить, что значение счетчика для статических объектов класса NSString (например @”I am a string”) равно -1, то есть максимально возможное. Вот пример работы со счетчиком:

id anObject = [SomeClass alloc]; //вначале счетчик == 1
[anObject init]; //тут создаются инварианты объекта
[anObject reatin]; //увеличим его значение (теперь он == 2)
[anObject release]; //уменьшим (счетчик опять == 1 и объект по прежнему жизнеспособен)
[anObject release]; //счетчик обнуляется, уменьшаются на 1 счетчики инвариантов и выделенная под объект память возвращается ОС

Реализация init очень важна. Это конструктор класса. Конструкторы отличаются тем, что возвращаеют id и их названия всегда начинается со слова init, а конструктор по умолчанию – это и есть просто init. Схема любого конструктора примерно следующая:

- (id)init
{
self = [super init]; //вызываем конструктор базового класса для
//инициализации его инвариантов
if (self) //если в конструкторе базового класса все прошло удачно
//и он вернул корректный объект, а не освободив память вернул nil
{
//то тут можно смело инициализировать свои инварианты
}
return self; //и возвращать самого себя
}

Вот типичный специализированный (не по умолчанию) конструктор для класса с двумя членами типа некоторого класса и одним целочисленным инвариантом:

- (id)initWithInt: (int)number
{
if (self = [super init])
{
_myMember1 = [[SomeClass alloc] init]; //все как положено: выделили память, затем ее инициализировали
_myMember2 = [[SomeClass alloc] init];
_myIntMember = number; //здесь конструктор ни к чему
//инициализируем переданным параметром
}
return self;
}

Реализация release и retain для NSObject идеологически примерно следующая, и ее не нужно переопределять в производных классах, в силу отсутствия доступа к инварианту счетчика ссылок:

- (void)retain
{
[_internalLock lock]; //блокировка для синхронизации
_referenceCounter++; // пусть _referenceCounter – скрытый инвариант счетчика
[_internalLock unlock];
}
- (void)release
{
[_internalLock lock];
_referenceCounter--; //уменьшим счетчик
if (!_referenceCounter) //если он равен нулю
{
[_internalLock unlock];
[self dealloc]; //скажем себе, что пора умирать (блокировка освободится тут)
}
[_internalLock unlock];
}

То есть самому объекту посылается сообщение dealloc, в реализации метода которого он может, по необходимости, уменьшить счетчики своих инвариантов и передать аналогичное сообщение объекту базового класса, чтобы он сделал то же самое. Очевидно, реализация метода dealloc для NSObject освободит память, выделенную объекту. Обычно dealloc для какого-нибудь класса выглядит так:

- (void)dealloc
{
[_myMember1 release]; //уменьшим счетчик своего инварианта 
[_myMember2 release]; //уменьшим счетчик другого своего инварианта
//[_myIntMember release]; это полный бред, т.к. встроенные типы сообщений не принимают вообще и счетчиков не ведут
[super dealloc]; //cкажем объекту базового класса, что пора освобождать память
}


Методы доступа


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

- (void)setRe: (double)re
{
_re = re;
}

Так как инвариант _re относится ко встроенному типу, никаких сложностей с изменением его значения не возникает. Но если инвариант – объект некоторого класса – то простым присваиванием не обойтись, ведь надо учитывать счетчики ссылок. Для решения этой проблемы применяются следующие три метода:

//например, нужно изменить текст у ярлыка
[label setText: @”Hello world”]; //устанавливаем инвариант text
//объекта label равным текстовой константе типа NSString *

//примерная реализация setText в классе UILabel (вариант №1)
- (void)setText: (NSString *)text
{
[text retain]; //увеличиваем счетчик ссылок на формальный параметр
[_text release]; //уменьшаем счетчик ссылок текущего значения своего инварианта _text
_text = text; //инициализируем инвариант новым значением
}

//примерная реализация setText в классе UILabel (вариант №2)
- (void)setText: (NSString *)text
{
if (_text != text) //cравниваем указатели на объекты
{
[_text release]; //уменьшаем счетчик ссылок текущего значения
//своего инварианта _text
_text = [text retain]; //увеличиваем счетчик ссылок
//на формальный параметр и инициализируем свой инвариант
}
}

//примерная реализация setText в классе UILabel (вариант №3 – нежелательный)
- (void)setText: (NSString *)text
{
if (_text != text)
{
[_text autorelease]; //скинем текущеe значения своего
//инварианта _text в самовыгружаемый пул
_text = [text retain]; //увеличиваем счетчик ссылок
//на формальный параметр и инициализируем свой инвариант
}
}

Вариант №3 не очень удачный потому, что засоряет текущий самовыгружаемый пул, а обычно это не очень желательно (см. следующий раздел).
Метод доступа для чтения значения инварианта всегда очень прост:

- (NSString *)text
{
return _text;
}


Самовыгружаемый пул в нитях программы


Теперь попробуем вернуть из метода созданный внутри него объект:

-(NSString *)sayHelloToName: (NSString *)name withSurname: (NSString *)surname 
{
NSString *retString = [[NSString alloc] initWithFormat: @”%@ %@!”, name, surname]; //инициализируем созданный объект посредством строки формата
return retString;
}

Строка формата соответствует стандарту языка С. Но если в ней необходимо указать тип id, то используется спецификатор формата %@. Каким образом метод, разбирающий формат, понимает какие символы подставить вместь id? Он просто подставит то, что вернет метод описания description данного объекта. Этот метод изначально объявлен для класса NSObject. NSString переопределил его на вывод своего строкового содержания. Переопределив его, любой объект может представлять свое строковое содержание. Например, так это может сделать класс комплексного числа с двумя инвариантами типа double:

- (NSString *)description
{
return [NSString stringWithFormat: @”re: %lf im: %lf”, _re, _im]; //возвращает строку @“re: 1.0 im: 2.5” для _re == 1.0 и _im == 2.5
}

После выполнения метода sayHelloToName:withSurname: определенно произойдет утечка памяти, так как вызывающий код скорей всего не догадывается, что возвращенному объекту нужно после обработки послать сообщение release. Даже если он догадается это сделать, возможно, что возвращался указатель на инвариант объекта, а значит его уничтожение чревато серьезными последствиями. Хотелось бы иметь механизм самоосвобождения объектов когда либо в будующем, чтобы пользовательский код вообще не думал об их освобождении. Решается эта проблема с помощью объекта класса NSAutoreleasePool – самовыгружаемого пула объектов.

После создания объекта такого класса всем объектам, созданным после него, можно послать сообщения autorelease. При этом данный объект помещается в текущий (последний созданный) самовыгружаемый пул. Когда некий пул получит сообщение release, то он отошлет такое же сообщение и всем своим объектам, уменьшая их счетчик ссылок (по сути, уничтожая). Таким образом. Объект, помещенный в самовыгружаемый пул, продолжает жить и занимать память во все время жизни пула. Это удобно для небольших временных объектов, но может с течением времени занять значительную часть памяти. Потому рекомендуется циклы, способные порождать большое количество временных объектов, которые отправляются в самовыгружаемый пул, обрамлять локальными (вложенными) пулами.

Любая нить в программе, использующей Cocoa, должна создавать объект класса NSAutoreleasePool в самом начале (прежде создания других объектов), и в самом конце его уничтожать (после уничтожения всех других объектов). Функция main(), являющаяся главной нитью любой программы на Objective-C, при использовании фреймворка Cocoa должна всегда выглядеть вот так:

int main(int argc, char *argv[]) // или же просто main()
{ 
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //создаем пул, он автоматически становится текущим
int retVal;
//теперь можно программировать спокойно
[pool drain]; //освобождаем пул и все объекты, помещенные в него вызовами autorelease
return retVal;
}

А корректный метод sayHelloToName:withSurname: теперь будет выглядеть вот так:

-(NSString *)sayHelloToName: (NSString *)name withSurname: (NSString *)surname 
{
NSString *retString = [[NSString alloc] initWithFormat: @”%@ %@!”, name, surname]; //инициализируем созданный объект посредством строки формата
[retString autorelease]; //помещаем в пул, теперь retString освободится вместе с пулом
return retString;
}

К слову, метод drain самовыгружаемого пула аналогичен release с той лишь разницей, что, кроме освобождения себя самомго и всех содержащихся объектов, еще дает подсказку сборщику мусора вступить в игру. Однако, это актуально только для Mac OS 10.4 и выше, так как на iOS сборки мусора нет.

Определение класса


Теперь рассмотрим файл Complex.m с определением методов класса Complex:

#import “Complex.h”

@implementation Complex
- (id)init
{
return [self initWithRe: 0.0 andIm: 0.0];
}
- (id)initWithRe: (double)re andIm: (double)im
{
if (self = [super init])
{
_re = re;
_im = im;
_format = @”re: %.1lf im: %.1lf”; //формат вывода по умолчанию
}
}
+ (Complex *)complexWithRe: (double)re andIm: (double)im
{
return [[[Complex alloc] initWithRe: re andIm: im] autorelease];
}
- (Complex *)add: (Complex *)other
{
return [[Complex alloc] initWithRe: _re + other->_re andIm: _im + other->_im];
}
- (Complex *)sub: (Complex *)other
{
return [[Complex alloc] initWithRe: _re – other->_re andIm: _im – other->_im];
}
- (NSString *)format
{
return _format;
}
- (void)setFormat: (NSString *)format
{//стандартный порядок действий для инварианта-объекта
[format retain];
[_format release];
_format = format;
}
- (double)re
{
return _re;
}
- (void)setRe: (double)re
{
_re = re;
}
- (double)im
{
return _im;
}
- (void)setIm: (double)im
{
_im = im;
}
- (NSString *)description
{//используем установленный формат вывода
return [NSString stringWithFormat: _format, _re, _im]; 
}
- (void)dealloc
{
[_format release]; //для этого и переопределялся dealloc
[super dealloc];
}
@end

Конструктор по умолчанию вызывает специализированный конструктор с определенными начальными параметрами. Метод complexWithRe:andIm: возвращает инициализированный объект класса Complex, размещенный в текущем самовыгружаемом пуле. То же самое делает и метод description, возвращая объект класса NSString. Вот пример программы, где используется класс Complex:

#import “Complex.h”
#import <stdio.h>  //для printf()

int main()
{ 
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Complex *num1 = [[Complex alloc] init]; //0.0+0.0*i
Complex *num2 = [[Complex alloc] initWithRe: 1.5 andIm: -2];
//1.5-2.0*i
Complex *num3 = [Complex complexWithRe: 5 andIm: 7];
//5.0+7.0*i
printf(“%s\n”, [[num2 description] cStringUsingEncoding: NSASCIIStringEncoding]); //вывод> re: 1.5 im: -2.0
printf(“%s\n”, [[[num2 add: num3] description] cStringUsingEncoding: NSASCIIStringEncoding]); //вывод> re: 6.5 im: 5.0
[num1 setRe: [num2 re]]; //задаем _re для num1 как у num2
[num1 setIm: [num3 im]]; //задаем _im для num1 как у num3
[num1 setFormat: @”%.2lf+%.2lf*i”]; //меняем формат вывода для num1
printf(“%s\n”, [[num1 description] cStringUsingEncoding: NSASCIIStringEncoding]); //вывод> 1.50+7.00*i
[num1 release];
[num2 release];
//[num3 release]; не нужно, т.к. он уже в самовыгружаемом пуле
[pool drain];
return 0;
}


Категории и расширения


Если к уже написанному (а, возможно, и откомпилированному) классу нужно добавить\переопределить некоторые методы без наследования – категории позволяют это сделать без особых усилий:

//файл “CategorizedComplex.h”

#import “Complex.h”
@interfce Complex (CategorizedComplex)
- (Complex *)mul: (Complex *)other;
- (Complex *)div: (Complex *)other;
@end

//файл “CategorizedComplex.m”
#import “CategorizedComplex.h”

@implementation Complex (CategorizedComplex)
- (Complex *)mul: (Complex *)other
{
return [Complex complexWithRe: _re * other->_re - _im * other->_im andIm: _re * other->_im + _im * other->_re];
}
- (Complex *)div: (Complex *)other
{
double retRe, retIm, denominator;
denominator = other->_re * other->_re + other->_im * other->_im;
if (!denominator)
return nil;
retRe = (_re * other->_re + _im * other->_im) / denominator;
retIm = (_im * other->_re - _re * other->_im) / denominator;
return [Complex complexWithRe: retRe andIm: retIm];
}
@end

А пользоваться этим можно вот так:

CategorizеdComplex *num1 = [[CategorizedComplex alloc] initWithRe: 1 andIm: 999];
Complex *num2 = [Complex complexWithRe: 0 andIm: 0];
CategorizedComplex *num3 = [num1 div: num2]; //num3 == nil

Расширения несут добрую службу как безымянные категории:

//файл “CategorizedComplex.m”
#import “CategorizedComplex.h”
@interface Complex ()
- (void)zeroComplex; //тайный метод для обнуления числа
@end

@implementation Complex
- (void)zeroComplex //им могут пользоваться только методы самого класса
{
_re = 0;
_im = 0; 
}
@end


Протоколы


Протокол Objective-C – это формализованное объявление группы методов, которые, по желанию, может реализовать любой класс (аналог класса в С++, где все методы объявлены со спецификатором virtual … = 0). В версии языка 2.0 методы протокола могут быть требуемыми (спецификатор @required, он считается умалчиваемым) и выборочными (спецификатор @optional). Если какой либо класс реализовал требуемые методы протокола, то он называется классом, поддерживающим данный протокол. Протокол, и класс, его поддерживающий, объявляются вот так:

@protocol MyPrinterProtocol
@required
- (void)print;
- (BOOL)switchedOn;
@optional
- (void)loadPapaer: (int)numberOfPages;
@end

@interface MyPrinter : NSObject <MyPrinterProtocol>
//теперь MyPrinter реализует методы MyPrinterProtocol
{
BOOL _state;
int _numberOfPages;
}
- (id)initWithState: (BOOL)state andPagesCount: (int)pages;
- (BOOL)state;
@end

Oбъекту класса MyPrinter можно гарантированно посылать сообщения print и switchedOn, и, после проверки на respondsToSelector:, можно посылать сообщение loadPaper:, та как в его реализации должны присутствовать определения одноименных методов. Объявление объекта класса, поддерживающего какой-либо протокол осуществляется так:

MyPrinter *printer;
id anotherPrinter = [[MyPrinter alloc] init];
[anotherPrinter print]; //безымянный объект отвечает на сообщение без предупреждений компилятора

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

@interface MyPrinter : NSObject <MyPrinterProtocol, OtherProtocol>

А чтобы объявить объект неизвестного класса (id), соответствующий некоторому протоколу, пишут так:

id <MyPrinterProtocol> somePrinter;

Исключения


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

Исключение – это объект некоторого класса. Он (даже своим типом) несет в себе некоторую информацию о произошедшей ситуации. Для удобства в Cocoa имеется класс NSException, который можно инициализировать двумя объектами NSString и одним объектом произвольного класса (тип id):

- (id)initWitnName: (NSString *)name reason: (NSString *)reason userInfo: (id)userInfo;

Сгенерировать исключение и, тем самым, запустить механизм раскрутки стека вызовов, можно с помощью оператора @throw. Чтобы перхватить сгенерированное исключение, участок кода, где возможна его генерация, необходимо заключить в специальный блок с заглавием try (такие блоки могут быть вложенными). А затем, после этого блока, поставить блок с заглавием catch(), где в круглых скобках указать тип предполагаемого исключения. Блоков catch() после блока try может быть несколько. После генерации исключения управление, раскручивая стек, выходит из блока try и, проверяя по очереди все блоки catch(), попадает именно в тот блок catch(), в фигурных скобках которого стоит такой тип, к которому тип исключения приводится неявно (точное совпадение, указатель на базовый класс или id). Если исключение по типу не совпало ни с одним блоком catch(), управление продолжает раскрутку стека. Если после блока с заглавием try стоит блок с заглавием finally, то управление передастся ему независимо от того, произошло ли в блоке try исключение (и обработан какой-нибудь блок catch()), или выполнилась его последняя инструкция. Ниже приведен пример работы с объектом класса Cup в методе fill которого происходит исключение:

Cup *cup = [[Cup alloc] init];
@try 
{
[cup fill]; //в fill генерируется исключение типа NSException
}
@catch (NSException *exception)
{//логируем произошедшее исключение с помощью NSLog
NSLog(@"main: Caught %@: %@", [exception name], [exception reason]);
}
@finally //сюда после @try мы попадем неизбежно
{
[cup release]; 
}

В блоке finally удобно освобождать ресурсы, выделенные в блоке try, но не освобожденные по причине сгенерированного исключения.

Свойства


Для версии Objective-C 2.0 нашa реализация класса Complex явно избыточна: в ней слишком много методов доступа и их определение – сплошная рутина. Перепишем его с использованием свойств:

//файл “Complex.h”
#import <Foundation/Foundation.h>  //для NSObject и строк NSString

@interface Complex : NSObject
{
double _re; //инвариант для действительной части
double _im; //инвариант для мнимой части
NSString *_format; //строка формата для метода description
}
- (id)initWithRe: (double)re andIm: (double)im; 
+ (Complex *)complexWithRe: (double)re andIm: (double)im; 
- (Complex *)add: (Complex *)other; //метод для сложения
- (Complex *)sub: (Complex *)other; //метод для вычетания
@property (nonatomic, retain) NSString *format; //объявим методы доступа
@property (nonatomic, assign) double re; //посредством объявления свойств
@property (nonatomic, assign) double im;
@end

//файл “Complex.m”
#import “Complex.h”

@implementation Complex
@synthesize format = _format; //сгенерируем методы доступа
@synthesize re = _re; //и заодно переменуим их
@synthesize im = _im; //чтобы в имени не было подчеркивания 
- (id)init
{
return [self initWithRe: 0.0 andIm: 0.0];
}
- (id)initWithRe: (double)re andIm: (double)im
{
if (self = [super init])
{
_re = re;
_im = im;
_format = @”re: %.1lf im: %.1lf”; //формат вывода по умолчанию
}
}
+ (Complex *)complexWithRe: (double)re andIm: (double)im
{
return [[[Complex alloc] initWithRe: re andIm: im] autorelease];
}
- (Complex *)add: (Complex *)other
{
return [[Complex alloc] initWithRe: _re + other.re andIm: _im + other.im]; //используем свойства re и im
}
- (Complex *)sub: (Complex *)other
{
return [[Complex alloc] initWithRe: _re – other.re andIm: _im – other.im]; //используем свойства re и im
}
@end

Свойство – это некоторое имя, доступное через указатель на объект посредством оператора точка “.”. Свойства используются вместо методов доступа чтобы получить или установить инвариант объекта. При объявлении свойства указывается рад параметров, описывающих особенности генерируемых свойством методов доступа.
  • getter=getterName, setter=setterName Указывает, что метод доступа для чтения будет называться getterName, а для изменения — setterName
  • readonly Не генерировать метод доступа для изменения
  • readwrite Генерировать оба метода доступа
  • assign Метод доступа на изменение реализовывать посредством простого присваивания
  • retain Принимаемому значению послать сообщение retain, предыдущему значению инварианта послать release и присвоить ему принимаемое значение
  • copy Использовать обычный оператор присваивания, но присвоить копию принимаемого значения (перед присваиванием емупосылается сообщение copy)
  • nonatomic Не использовать внутренние блокировки для синхронизации нескольких нитей в сгенерированных методах доступа (по умолчанию cинхронизация используется)
Теперь в определении класса Complex нам не нужно вручную писать методы доступа. Они сгенерируются компилятором и будут идентичны тем, что были раньше.

Удачи!
Антон Шумихин @shoumikhin
карма
18,7
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (61)

  • +1
    Можно добавить еще блоки (http://pragmaticstudio.com/blog/2010/7/28/ios4-blocks-1), synthesize by default и возможность объявлять переменные в расширениях(http://www.mcubedsw.com/blog/index.php/site/comments/new_objective-c_features/). Для последних двух фич нужен компилятор LLVM 1.5 или 2.0
  • +2
    Эх… где ж вы были пол годика назад?!
    Но пост отличный, большое спасибо.
    Будет ли продолжение?
    • +5
      Спасибо. Конечно, но уже не чисто о языке, а о реальных проектах с его применением.
      • 0
        очень интересно будет почитать
  • +8
    Это не «5 копеек», судя по тому сколько оборотов колесика мышки мне пришлось сделать чтобы добраться до комментариев, тут рублей 500 как минимум.
    • 0
      Это по сравнению с официальной документацией :)
    • НЛО прилетело и опубликовало эту надпись здесь
  • +5
    У Вас есть очень грубая ошибка в тексте:
    > id myObject; //будущий бъект myObject произвольного класса
    > myObject = [MyClass alloc]; //метод класса alloc выделяет участок памяти нужного размера и возвращает указатель на него
    > [myObject init]; //метод объекта init иннициализирует инварианты объекта myObject

    Это очень серьезная ошибка! Никогда нельзя разделять строку myObject = [[MyClass alloc] init], это связанно с тем, что результат метода init не всегда возвращает указатель на ту же область, что и alloc! И этот момент отлично расписан в официальной документации! Так же с этим связана строка self = [super init] в конструкторах.
    • 0
      Спасибо, исправил.
    • 0
      Почему нельзя разделять:)
      можно написать вот так (хотя естественно исходный вариант опасный):
      id obj = [MyClass alloc];
      obj = [obj init];
    • 0
      Постойте, но ведь в данном случае результат метода init нигде не сохраняется и не используется. Разъясните, пожалуйста.
      • 0
        Ошибка как раз в том, что он «не сохраняется и не используется». Метод alloc лишь выделяет память, метод init может вернуть не тот результат, который был возвращен методом alloc. Самы простой пример: метод initWith… вернет nil в случае неправильных параметров, но если Вы не будете использовать этот результат и не сохраните его, то даже не узнаете о возможной ошибке.
        • 0
          Я понял — указатель, который необходимо использовать, возвращается методом init, а не alloc.
          Благодарю за разъяснение!
  • 0
    Объясните, пожалуйста, незнакомому с этой проблемой человеку, разве нет компиляторов нормальных языков под яблочные платформы? Я слышал, что есть компиляторы для c++, и что не обязательно изучать objective-c чтобы писать под мак, айфон и т.п.
    • НЛО прилетело и опубликовало эту надпись здесь
    • +1
      Чтобы писать программы одного компилятора недостаточно, нужна стандартная библиотека.
      А для Маков такая библиотека (аж целый фреймворк!) — это Cocoa. Вот она-то и писана на Objective-C.
      • 0
        Cocoa не фреймворк — это Objective-C (си с обьектами) и набор фреймворков — AppKit и Foundation Kit. По сути, как Delphi под Windows — «паскаль с объектами», VCL и прочие мелкие плюшки. При этом под обе системы вполне можно писать и без Cocoa/Delphi, просто с ними быстрее и удобней.
    • +1
      для десктопа XCode умеет C, C++, Fortran, Objective-C, Objective-C++, Java, Applescript, Python and Ruby
      для iPhone — тока Objective-C.
      Есть monoTouch, который позволяет на c# писать с кучей ограничений. Мне не особо понравилось, да и библиотеки другие — разбираться придётся почти столько же.
      Вроде для с++ что-то было
      • 0
        Немного дополню.
        Objective-C полностью совместим с С и С++. Поэтому, если сильно не нужен Cocoa Touch (например, для игр) можно писать используя Objective-C по минимуму.
        Кроме MonoTouch еще есть:
        Appcelerator Titanium, PhoneGap — Javascript
        Rhodes — Ruby
        Airplay SDK — С++, возможна разработка из под Windows
        Кроме айфона вышеперечисленные инструменты поддерживают и другие платформы
    • 0
      Что значит «нормальных» языков? Objective С не только синтаксисом отличается, если вы не заметили :)
    • 0
      Про десктоп уже сказали, а для iOS можно писать на Obj-C только интерфейс, а остальное на C/C++ — без проблем. Если прога кроссплатформенная, то так и делают (всё, что можно — на «плюсах»).

      Сейчас Эппл сняли ограничения не фреймворки, думаю, в течение полугода-года будет Qt для iOS на базе Cocoa или Qt Lighthouse/QPA, да и что-то ещё подтянется.
      • 0
        Уточнение: и Qt поверх Cocoa, и Lighthouse поверх Cocoa уже есть в разработке, и у второго есть шанс дорасти до юзабельного состояния реально быстро (за недели). Как результат, самый продвинутый C++ фреймворк будет и на iPhone.
  • +1
    Отличная статья
  • 0
    Опечатка:
    Посыка сообщения, как и любая функция C, возвращает определенное (может void) значение:
  • –1
    Я б добавил в статью проверку на наличие объявленного класса, рядом с проверкой сообщений. Для общей картины.
    К примеру:
    Class gcClass = (NSClassFromString(@«GKLocalPlayer»));
    if(gcClass) {
    //true
    } else {
    //false
    }

    Спасибо за подробную статью.
    Повторенье — мать учения!
  • 0
    У вас ошибка, в примере с NSAutoreleasePool в конце функции вы вызываете [pool release];, это неверно, метод release класса NSAutoreleasePool не выполняет каких либо действий, для очистки пула надо слать сообщение [pool drain];
    • 0
      Извиняюсь, данное поведение характерно при использовании GC, в отсутсвии GC можно использовать и [pool release];
      • 0
        Все ok, спасибо! drain конечно. То был мой недосмотр.
  • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Мда, и его я решил учить, купив MBP. :( много. :)
  • +1
    Спасибо за статью! Было бы очень интересно, если бы Вы написали статьи по созданию каких-нибудь небольших простых и интересных примеров приложений под iPhone/iPad с комментариями и описанием.
  • –2
    Your grandma's Programming Language…
  • +1
    Очень прошу общее введение в ООП (до абзаца с «Objective-C возник в 80-x как модификация С в сторону Smalltalk. „) отделить под отдельный заголовок. Для тех, кто про ООП уже слышал и зашёл под кат почитать именно про Objective-C. А то до той строчки текста много, но как-то ну совсем ничего нового. Пока там читаешь, кажется что остальная статья будет в том же духе, поверхностна банальна.

    А в том первом абзаце про Objective-C надо б чуть пораньше обозначить похожесть на С++ и не забыть перечислить отличия, или хотя бы просто сказать (анонсировать), что они есть.
    • 0
      Многие знают о ООП по сям или джаве. А Obj-C — это другая трубка и курить ее надо с другой стороны ;)
      • 0
        ничего этакого про другую сторону в том разделе я лично не вижу… Обычное поверхностное экспресс-введение в методологии программирования, вплоть до ООП.
        • 0
          Во-первых, главный камень преткновения — терминология (хотя ИМХО как раз этот момент в статье раскрыт плохо), во-вторых, если я правильно понял, есть различия в логике работы.
  • +2
    При первом упоминании NS в именах классов стоило бы сказать, как оно расшифровывается…
  • +1
    А в целом — спасибо!
  • +1
    в примере с протоколами нужно указать что класс MyPrinter реализует протокол MyPrinterProtocol, вот так

    @interface MyPrinter: NSObject <MyPrinterProtocol>

    иначе класс не обязан отзываться на селекторы из протокола

    еще недавно октрыл для себя, что класс можно «заставить» следовать протоколу, уже на этапе добавления категорий
    вот такие вещи вполне работают
    @interface MyClass () <NewProtocol>
    @interface MyClass (NewCategory) <NewProtocol>

    ну и объявить указатель на объект «не знаю какого класса, но знаю что реализует протокол» таким образом:
    id <SomeProtocol> delegate;
    и можно вызывать у delegate методы из протокола SomeProtocol без ругани со стороны компилятора

    и вообще статья отличная, спасибо
    • 0
      Если вы прочитаете официальную документацию, то у вас больше не будет «открытий», поверьте =)
      • –1
        А она есть на русском? А эта статья теперь есть ;)
        • +1
          Нужно признать, что подобные мелочи на русском как правило всеравно не описываются, и даже если посмотреть на ссылки про блоки выше в комментариях, то там также описан лишь самые основы. Исключительно с русским — это не серьезно.
  • +1
    Если к уже написанному (а, возможно, и откомпилированному) классу нужно добавить некоторые методы, а не переопределять его собственные или добавлять инварианты (для чего вообще-то и нужно наследование)...

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

    Добавление к существующему классу своих переменных так же широко используется, для этого пользуются связкой из ассоциативного хранилища и KVC. Причем последние реализации категорий в llvm уже позволяют добавлять свои переменные класса к чужому классу, так что это будущее развитие и данность.
    • 0
      Я правильно понимаю, что это практически то самое, что в других кругах называется «Аспектно-ориентированное программирование»?
      • 0
        Точнее одна из плюшек, если не ошибаюсь, в терминологии АОП категории будут именоваться inter-type, т.е. то, что позволяет динамически изменять стуктуру программы. Но например с их помощью вы уже не получите join-point'ы, потому что доступа к исходной реализации заменяемого метода нет (а как было бы красиво), впрочем это уже мелочи ибо подобные вопросы рещаются легко на уровне самого исходного кода, а вот без категорий пришлось бы куда сложнее и менее элегантно.
  • –1
    Спасибо за статью!

    Конечно, очень режет глаза синтаксис Objective-C, который просто чудовищно страшный.
    При том что это ведь по сути достаточно высокоуровневый язык (компилируемый в чистый C по ходу, если я верно понял), то ведь можно было бы вместо него сделать что угодно — что-нибудь в разы более эстетичнее и читабельней. Что-нибудь Явообразное, например.
    Какой-то каменный век…

    Да и когнинивный диссонанс с образом Эппла, помешанной на красоте и простоте.
    • +2
      На самом деле он чудовищно челевечный и красивый, а вы просто не привыкли к синтаксису, ну и вдобавок не видели правил именования, благодаря которым любая программа вам становится понятна с первого же знакомства и нигде никогда не нужно ломать голову, что же тут происходит или читать документацию для методов какого-либо чужого класса :)

      Сам же Obj-C — это по сути синтаксический сахар над C, можно переписать программу с Obj-C исключительно на C, просто классы заменятся структурами, вызовы методов функциями и т.д.
    • +2
      не знаю, не знаю, полгода пишу на Objective-C и скажу что после него мне трудно уже писать на С++ и JAVA.
      Сам язык по началу показался страшный ужасть, но суть тут не в языке.
      Тут особенность в том что у Эппл замечательные фреймворки и хороший редактор которые дополняют язык.
      А объектная система и протоколы в Objective-c со временем кажуться намного более простыми и удобными чем традиционные решения Java /C++. ИМХО
      З.Ы.
      Хотя своего гемороя тут навалом, нужно признать
      • 0
        Спасибо за воодушевление.

        Но на вид там конечно дикий зоопарк. И обычные скобки, и квадратные и фигурные. И плюсики и минусики и собачки. И звездочки, которые при этом вовсе не означают умножение. В общем такого винегрета давно не видел.
        Семантика явно хромает.
        • 0
          На вид там ужас программиста, но я повторяю — только на вид)
          Даже собачка там имеет пользу. Потому что нажав @ + пара первых символов макра,
          тебе автоматически его предлагают, достаточно нажать интер. Это очень ускоряет написание различных. На других языках я не знаю как бы можно было различить переменную и макро так сходу.
          Да и вообще в XCode одно из лучших автодополнений что я видел.Через некоторое время забываешь про все эти условности, и то что раньше Objective-C казался вырви глазом, я сейчас даже не помню точно названия большинства методов что использую, потому что набираю их автоматом).
          Честно говоря не смотря на кажущуюся сложность, я например в Objective-C въехал за пару недель (в рамках написать простенькое приложение с анимацией и webview), а вот в MFC и Windows API въезжал намного дольше).
          В моем представлении Objective-C это даже не язык а такой набор макр для С, потому что когда я пишу что то на нем, я могу предположить в какой примерно С код это потом переделается.
          З.Ы.
          много написал, видимо потому что давно искал шанса сделать такое сравнение).

  • 0
    статтья слишком большая, оборвалась, а так "+" — фундаментально! хотя и ничего нового, но повторил на ура :)
  • 0
    Единственный топик, после прошествия стольки времени, который больше в 4 раза комментариев :)
    Thx!
  • 0
    Большое спасибо за статью! Начал копаться в разработке под iOS, по книжке-для-чайников ничего не было понятно до вашей статьи, сейчас многое прояснилось.
    Возник вопрос: вот у вас в каких-то методах autorelease стоит (типа [Complex withRe: 1 andIm: 1] ), а в каких-то его может не быть. Как вообще узнать, выполняет ли, например, библиотечный метод autorelease к результату или нет? Ну и ещё вопрос: что будет, если я напишу [[obj autorelease] autorelease] (ну или применю autorelease к тому методу класса Complex, который это автоосвобождение уже осуществляет)?

    Ещё один вопрос. Есть ли в ObjectiveC перехват сообщений, для которых не нашлось селектора? типа method_missing в ruby (они ведь оба от смолтока взяли свои ООП).
  • 0
    Большое спасибо за статью!

    Единственно, что все время сбивало с толку — это неправильно употребление термина «инвариант». Инвариант — это не член данных класса, защищенный или публичный. Это условие, которое всегда сохраняется для данного экземпляра класса. Детальнее можно посмотреть здесь www.rsdn.ru/forum/cpp/1165010.all.aspx
  • 0
    Просьба не воспринимать мой комментарий, как личную критику, поскольку это лишь желание разобраться в деталях. Но мне как новичку в Objective-C было крайне тяжело понять вашу интерпретацию официальной документации, сложнее, чем читать специализированные книги в оригинале.
    Например, зачем вообще использовать термин «инвариант» и называть им всё подряд?
    «…Основным преимуществом объектно-ориентированного подхода стала возможность создавать новые классы на основе уже написанных (добавлять инварианты и методы, переопределять методы…»

    Здесь вместо «инварианты» подошло бы распространённое «переменные» или хотя-бы «свойства». Но это было не суть важно, если бы не дальше:
    «…Инвариант isa относится к зарезервированному типу Class…»

    Тоесть isa тоже инвариант? Но ведь это не переменная, это указатель, который является лишь частью переменной или объекта.

    «Строковая константа такого класса записывается как @”Hello world”»

    @«Hello world» -это объект класса NSString. Почему он константа?

    «Чтобы заставить объект выполнить какой-нибудь метод нужно послать ему сообщение, именуемое так же, как и требуемый метод. Такое сообщение называется селектор метода. Синтаксис посылки таков:
    [receiver method];»

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

    «Методы с неограниченным количством аргументов вызываюся следующим синтаксисом:
    [receiver undefinedNumberParameters: one, two, three, four, five, six, seven];»

    А как же «nil» в конце, который указывает на завершение инициализации?

    «использовать определенные в базовом классе методы как свои), названное наследованием.»

    В «базовом классе» это частный случай. Корректнее было бы «в родительском классе».

    «Oбъекту класса MyPrinter можно гарантированно посылать сообщения print и switchedOn…»

    Нельзя. Класс MyPrinter должен сначала реализовать все required методы протокола. Разве нет?

    «Свойство – это некоторое имя, доступное через указатель на объект посредством оператора точка “.”…»

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

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