Работа с объектами
Большинство работы в приложении Objective C происходит в результате сообщений, отправляемых назад и вперед через экосистему объектов. Некоторые из этих объектов являются экземплярами классов, предоставленных Касанием Какао или Какао, некоторые - экземпляры Ваших собственных классов.
Предыдущая глава описала синтаксис для определения интерфейса и реализации для класса, включая синтаксис для реализации методов, содержащих код, который будет выполняться в ответ на сообщение. Эта глава объясняет, как отправить такое сообщение в объект и включает покрытие некоторых динамических функций Objective C, включая динамический контроль типов и возможность определить, какой метод должен быть вызван во время выполнения.
Прежде чем объект может использоваться, он должен быть создан должным образом с помощью комбинации выделения памяти для ее свойств и любой необходимой инициализации ее внутренних значений. В этой главе описываются, как вложить вызовы метода, чтобы выделить и инициализировать объект, чтобы гарантировать, что она сконфигурирована правильно.
Объекты отправляют и получают сообщения
Несмотря на то, что существует несколько различных способов отправить сообщения между объектами в Objective C, безусловно наиболее распространенным является базовый синтаксис, использующий квадратные скобки, как это:
[someObject doSomething]; |
Ссылка слева, someObject
в этом случае, получатель сообщения. Сообщение справа, doSomething
, имя метода для обращения к тому получателю. Другими словами, когда вышеупомянутая строка кода выполняется, someObject
будет отправлен doSomething
сообщение.
Предыдущая глава описала, как создать интерфейс для класса, как это:
@interface XYZPerson : NSObject |
- (void)sayHello; |
@end |
и как создать реализацию того класса, как это:
@implementation XYZPerson |
- (void)sayHello { |
NSLog(@"Hello, world!"); |
} |
@end |
Принятие Вы овладели XYZPerson
объект, Вы могли отправить его sayHello
обменивайтесь сообщениями как это:
[somePerson sayHello]; |
Отправка сообщения Objective C концептуально очень походит на вызывание функции C. Рисунок 2-1 показывает эффективный процесс выполнения программы для sayHello
сообщение.
Для указания получателя сообщения важно понять, как указатели используются для обращения к объектам в Objective C.
Используйте указатели для отслеживания объекты
C и Objective C используют переменные для отслеживания значения, точно так же, как большинство других языков программирования.
Существует много основных скалярных тип переменных, определенных в стандарте C, включая целые числа, числа с плавающей точкой и символы, объявляющиеся и присвоившие значения как это:
int someInteger = 42; |
float someFloatingPointNumber = 3.14f; |
Локальные переменные, которые являются переменными, объявленными в методе или функции, как это:
- (void)myMethod { |
int someInteger = 42; |
} |
ограничиваются в объеме методом, в котором они определяются.
В этом примере, someInteger
объявляется как локальная переменная внутри myMethod
; как только выполнение достигает закрывающей фигурной скобки метода, someInteger
больше не будет доступно. Когда локальная скалярная переменная (как int
или a float
) уходит, значение исчезает также.
Объекты Objective C, в отличие от этого, выделяются немного по-другому. Объекты обычно имеют более длинную жизнь, чем простой объем вызова метода. В частности объект часто должен оставаться в живых дольше, чем исходная переменная, создававшаяся для отслеживания его, таким образом, память объекта выделена и освобождена динамично.
Это требует, чтобы Вы использовали указатели C (которые содержат адреса памяти) отслеживать их расположение в памяти, как это:
- (void)myMethod { |
NSString *myString = // get a string from somewhere... |
[...] |
} |
Несмотря на то, что объем переменной указателя myString
(звездочка указывает, что это - указатель), ограничивается объемом myMethod
, фактическая строка возражает, что указывает на в памяти, может иметь более длинную жизнь вне того объема. Это могло бы уже существовать, или Вы, возможно, должны были бы раздать объект в вызовах дополнительного метода, например.
Можно передать объекты для параметров метода
Если необходимо провести объект при отправке сообщения, Вы предоставляете объектный указатель для одного из параметров метода. Предыдущая глава описала синтаксис для объявления метода с единственным параметром:
- (void)someMethodWithValue:(SomeType)value; |
Синтаксис для объявления метода, берущего строковый объект, поэтому, похож на это:
- (void)saySomething:(NSString *)greeting; |
Вы могли бы реализовать saySomething:
метод как это:
- (void)saySomething:(NSString *)greeting { |
NSLog(@"%@", greeting); |
} |
greeting
указатель ведет себя как локальная переменная и ограничивается в объеме только к saySomething:
метод, даже при том, что фактическая строка возражает, что это указывает на, существовал до метода, вызываемого, и будет продолжать существовать после того, как метод завершается.
Методы могут возвращаемые значения
А также передавая значения через параметры метода, для метода возможно возвратить значение. Каждый метод, показанный в этой главе до сих пор, имеет тип возврата void
. C void
ключевое слово означает, что метод ничего не возвращает.
Указание типа возврата int
средние значения, что метод возвращает скалярное целочисленное значение:
- (int)magicNumber; |
Реализация метода использует C return
оператор для указания значения, которое должно пасоваться назад после метода, закончил выполняться, как это:
- (int)magicNumber { |
return 42; |
} |
Совершенно приемлемо проигнорировать факт, что метод возвращает значение. В этом случае magicNumber
метод не делает ничего полезного кроме возврата значение, но нет ничего неправильно с вызовом метода как это:
[someObject magicNumber]; |
Если действительно необходимо отслеживать возвращенное значение, можно объявить переменную и присвоить ее результату вызова метода, как это:
int interestingNumber = [someObject magicNumber]; |
Вы можете эхо-сигналы от методов просто тем же способом. NSString
класс, например, предлагает uppercaseString
метод:
- (NSString *)uppercaseString; |
Это используется таким же образом в качестве метода, возвращая скалярное значение, несмотря на то, что необходимо использовать указатель для отслеживания результат:
NSString *testString = @"Hello, world!"; |
NSString *revisedString = [testString uppercaseString]; |
Когда этот вызов метода возвращается, revisedString
укажет на NSString
объект, представляющий символы HELLO WORLD!
.
Помните что при реализации метода для возврата объекта, как это:
- (NSString *)magicString { |
NSString *stringToReturn = // create an interesting string... |
return stringToReturn; |
} |
строковый объект продолжает существовать, когда он передается как возвращаемое значение даже при том, что stringToReturn
указатель выходит из объема.
В этой ситуации существуют некоторые соображения управления памятью: возвращенный объект (создаваемый на «куче») должен существовать достаточно долго для него, чтобы использоваться исходной вызывающей стороной метода, но не навсегда, потому что это создало бы утечку памяти. По большей части функция Automatic Reference Counting (ARC) компилятора Objective C заботится об этих соображениях для Вас.
Объекты могут отправить сообщения себе
Каждый раз, когда Вы пишете реализацию метода, у Вас есть доступ к важному скрытому значению, self
. Концептуально, self
способ относиться к “объекту, это получило это сообщение”. Это - указатель, точно так же, как greeting
значение выше, и может использоваться для вызова метода на текущем принимающем объекте.
Вы могли бы решить осуществить рефакторинг XYZPerson
реализация путем изменения sayHello
метод для использования saySomething:
метод, показанный выше, таким образом перемещаясь NSLog()
вызовите к отдельному методу. Это означало бы, что Вы могли добавить дальнейшие методы, как sayGoodbye
, это каждый вызвало бы через к saySomething:
метод для обработки фактического процесса приветствия. Если бы Вы позже хотели вывести на экран каждое приветствие в текстовом поле в пользовательском интерфейсе, то необходимо было бы только изменить saySomething:
метод вместо того, чтобы иметь необходимость пройти через и скорректировать каждый метод приветствия индивидуально.
Новое использование реализации self
вызывать сообщение на текущем объекте было бы похоже на это:
@implementation XYZPerson |
- (void)sayHello { |
[self saySomething:@"Hello, world!"]; |
} |
- (void)saySomething:(NSString *)greeting { |
NSLog(@"%@", greeting); |
} |
@end |
Если Вы отправили XYZPerson
возразите sayHello
сообщение для этой обновленной реализации, эффективный процесс выполнения программы был бы как показано на рисунке 2-2.
Объекты могут вызвать методы, реализованные их суперклассами
Существует другое важное ключевое слово, доступное Вам в Objective C, вызванном super
. Отправка сообщения к super
способ вызвать через к реализации метода, определенной суперклассом далее цепочка наследования. Наиболее популярный способ использования super
при переопределении метода.
Скажем, Вы хотите создать новый тип класса лица, “класса” лица крика, где каждое приветствие выведено на экран с помощью прописных букв. Вы могли копировать все XYZPerson
класс и изменяет каждую строку в каждом методе для верхнего регистра, но самый простой путь состоял бы в том, чтобы создать новый класс, наследовавшийся от XYZPerson
, и просто переопределение saySomething:
метод так, чтобы это вывело на экран приветствие в верхнем регистре, как это:
@interface XYZShoutingPerson : XYZPerson |
@end |
@implementation XYZShoutingPerson |
- (void)saySomething:(NSString *)greeting { |
NSString *uppercaseGreeting = [greeting uppercaseString]; |
NSLog(@"%@", uppercaseGreeting); |
} |
@end |
Этот пример объявляет дополнительный указатель строки, uppercaseGreeting
и присваивает его, значение возвратилось из отправки оригинала greeting
возразите uppercaseString
сообщение. Как Вы видели ранее, это будет новым строковым объектом, созданным путем преобразования каждого символа в исходной строке к верхнему регистру.
Поскольку sayHello
реализован XYZPerson
, и XYZShoutingPerson
установлен наследоваться от XYZPerson
, можно вызвать sayHello
на XYZShoutingPerson
объект также. Когда Вы вызываете sayHello
на XYZShoutingPerson
, вызов к [self saySomething:...]
будет использовать переопределенную реализацию и отображать приветствие как верхний регистр, приводящий к эффективному процессу выполнения программы, показанному на рисунке 2-3.
Если Вы действительно решили позже изменить, новая реализация не идеальна, однако, потому что XYZPerson
реализация saySomething:
вывести на экран приветствие в элементе пользовательского интерфейса, а не через NSLog()
, необходимо было бы изменить XYZShoutingPerson
реализация также.
Лучшая идея состояла бы в том, чтобы измениться XYZShoutingPerson
версия saySomething:
вызывать через к суперклассу (XYZPerson
) реализация для обработки фактического приветствия:
@implementation XYZShoutingPerson |
- (void)saySomething:(NSString *)greeting { |
NSString *uppercaseGreeting = [greeting uppercaseString]; |
[super saySomething:uppercaseGreeting]; |
} |
@end |
Эффективный процесс выполнения программы, теперь следующий из отправки XYZShoutingPerson
возразите sayHello
сообщение показано на рисунке 2-4.
Объекты создаются динамично
Как описано ранее в этой главе, память выделяется динамично для объекта Objective C. Первый шаг в создании объекта должен удостовериться, что достаточно памяти выделяется не только для свойств, определенных классом объекта, но также и свойствами, определенными на каждом из суперклассов в его цепочке наследования.
NSObject
корневой класс обеспечивает метод класса, alloc
, который обрабатывает этот процесс для Вас:
+ (id)alloc; |
Заметьте, что тип возврата этого метода id
. Это - специальное ключевое слово, используемое в Objective C для значения “некоторого объекта”. Это - указатель на объект, как (NSObject *)
, но является особенным в этом, это не использует звездочку. Это описано более подробно далее в этой главе, в Objective C Динамический Язык.
alloc
метод имеет одну другую важную задачу, которая должна убрать память, выделенную для свойств объекта путем обнуления их. Это избегает обычной проблемы памяти, содержащей мусор от того, что было сохранено прежде, но недостаточно для инициализации объекта полностью.
Необходимо объединить вызов к alloc
с вызовом к init
, другой NSObject
метод:
- (id)init; |
init
метод используется классом, чтобы удостовериться, что его свойства имеют подходящие начальные значения при создании, и покрыт более подробно в следующей главе.
Обратите внимание на то, что init
также возвраты id
.
Если один метод возвращает объектный указатель, возможно вложить вызов к тому методу как получатель в вызове к другому методу, таким образом комбинируя многократные вызовы сообщения в одном операторе. Корректный способ выделить и инициализировать объект состоит в том, чтобы вложить alloc
вызовите в вызове к init
, как это:
NSObject *newObject = [[NSObject alloc] init]; |
Этот пример устанавливает newObject
переменная для указания на недавно создаваемый NSObject
экземпляр.
Самый внутренний вызов выполняется сначала, таким образом, NSObject
класс отправляется alloc
метод, возвращающийся недавно выделенный NSObject
экземпляр. Этот возвращенный объект тогда используется в качестве получателя init
сообщение, само возвращающее объект назад, чтобы быть присвоенным newObject
указатель, как показано на рисунке 2-5.
Методы инициализатора могут взять параметры
Некоторые объекты должны быть инициализированы с требуемыми значениями. NSNumber
объект, например, должен быть создан с числовым значением, которое он должен представлять.
NSNumber
класс определяет несколько инициализаторов, включая:
- (id)initWithBool:(BOOL)value; |
- (id)initWithFloat:(float)value; |
- (id)initWithInt:(int)value; |
- (id)initWithLong:(long)value; |
Методы инициализации с параметрами призваны просто тот же путь как плоскость init
методы — NSNumber
объект выделяется и инициализируется как это:
NSNumber *magicNumber = [[NSNumber alloc] initWithInt:42]; |
Методами фабрики классов является альтернатива выделению и инициализации
Как упомянуто в предыдущей главе, класс может также определить методы фабрики. Методы фабрики предлагают альтернативу традиционному alloc] init]
процесс, без потребности вложить два метода.
NSNumber
класс определяет несколько методов фабрики классов для соответствия ее инициализаторов, включая:
+ (NSNumber *)numberWithBool:(BOOL)value; |
+ (NSNumber *)numberWithFloat:(float)value; |
+ (NSNumber *)numberWithInt:(int)value; |
+ (NSNumber *)numberWithLong:(long)value; |
Метод фабрики используется как это:
NSNumber *magicNumber = [NSNumber numberWithInt:42]; |
Это - эффективно то же как предыдущее использование в качестве примера alloc] initWithInt:]
. Методы фабрики классов обычно просто вызывают сквозной к alloc
и соответствующее init
метод, и предоставлен для удобства.
Используйте новый для Создания Объекта, Если Никакие Параметры не Необходимы для Инициализации
Также возможно создать экземпляр класса с помощью new
метод класса. Этим методом предоставлены NSObject
и не должен быть переопределен в Ваших собственных подклассах.
Это - эффективно то же как вызов alloc
и init
без параметров:
XYZObject *object = [XYZObject new]; |
// is effectively the same as: |
XYZObject *object = [[XYZObject alloc] init]; |
Предложение литералов краткий синтаксис создания объекта
Некоторые классы позволяют Вам использовать более краткий, литеральный синтаксис для создания экземпляров.
Можно создать NSString
экземпляр, например, с помощью специальной литеральной нотации, как это:
NSString *someString = @"Hello, World!"; |
Это - эффективно то же как выделение и инициализация NSString
или использование одного из его методов фабрики классов:
NSString *someString = [NSString stringWithCString:"Hello, World!" |
encoding:NSUTF8StringEncoding]; |
NSNumber
класс также позволяет множество литералов:
NSNumber *myBOOL = @YES; |
NSNumber *myFloat = @3.14f; |
NSNumber *myInt = @42; |
NSNumber *myLong = @42L; |
Снова, каждым из этих примеров является эффективно то же как использование соответствующего инициализатора или метода фабрики классов.
Можно также создать NSNumber
использование помещенного в коробку выражения, как это:
NSNumber *myInt = @(84 / 2); |
В этом случае выражение оценено, и NSNumber
экземпляр создается с результатом.
Objective C также поддерживает литералы для создания неизменный NSArray
и NSDictionary
объекты; они обсуждены далее в Значениях и Наборах.
Objective C является динамическим языком
Как отмечалось ранее, необходимо использовать указатель для отслеживания объект в памяти. Из-за динамического характера Objective C не имеет значения, какой определенный тип класса Вы используете для того указателя — корректный метод будут всегда вызывать на соответствующем объекте, когда Вы отправите ему сообщение.
id
тип определяет указатель родового объекта. Возможно использовать id
при объявлении переменной, но Вы теряете информацию о времени компиляции об объекте.
Рассмотрите следующий код:
id someObject = @"Hello, World!"; |
[someObject removeAllObjects]; |
В этом случае, someObject
укажет на NSString
экземпляр, но компилятор ничего не знает о том экземпляре вне факта, что это - некоторый объект. removeAllObjects
сообщение определяется некоторыми Сенсорными объектами Какао или Какао (такой как NSMutableArray
) таким образом, компилятор не жалуется, даже при том, что этот код генерировал бы исключение во время выполнения потому что NSString
объект не может ответить на removeAllObjects
.
Перезапись кода для использования статического типа:
NSString *someObject = @"Hello, World!"; |
[someObject removeAllObjects]; |
средние значения, что компилятор теперь генерирует ошибку потому что removeAllObjects
не объявляется ни в какой общественности NSString
интерфейс, о котором это знает.
Поскольку класс объекта определяется во время выполнения, это не имеет никакого значения, что вводит Вас, присваивают переменную при создании или работе с экземпляром. Использовать XYZPerson
и XYZShoutingPerson
классы описали ранее в этой главе, Вы могли бы использовать следующий код:
XYZPerson *firstPerson = [[XYZPerson alloc] init]; |
XYZPerson *secondPerson = [[XYZShoutingPerson alloc] init]; |
[firstPerson sayHello]; |
[secondPerson sayHello]; |
Несмотря на то, что оба firstPerson
и secondPerson
со статическим контролем типов как XYZPerson
объекты, secondPerson
укажет, во время выполнения, к XYZShoutingPerson
объект. Когда sayHello
метод вызывают на каждом объекте, корректные реализации будут использоваться; для secondPerson
, это означает XYZShoutingPerson
версия.
Определение равенства объектов
Если необходимо определить, совпадает ли один объект с другим объектом, важно помнить, что Вы работаете с указателями.
Стандарт C оператор равенства ==
используется для тестирования равенства между значениями двух переменных, как это:
if (someInteger == 42) { |
// someInteger has the value 42 |
} |
При контакте с объектами == оператор используется, чтобы протестировать, указывают ли два отдельных указателя на тот же объект:
if (firstPerson == secondPerson) { |
// firstPerson is the same object as secondPerson |
} |
Если необходимо протестировать, представляют ли два объекта те же данные, необходимо вызвать метод как isEqual:
, доступный от NSObject
:
if ([firstPerson isEqual:secondPerson]) { |
// firstPerson is identical to secondPerson |
} |
Если необходимо выдержать сравнение, представляет ли один объект большее или меньшее значение, чем другой объект, Вы не можете использовать стандарт C операторы сравнения >
и <
. Вместо этого основные типы Основы, как NSNumber
, NSString
и NSDate
, обеспечьте a compare:
метод:
if ([someDate compare:anotherDate] == NSOrderedAscending) { |
// someDate is earlier than anotherDate |
} |
Работа с нолем
Это всегда - хорошая идея инициализировать скалярные переменные в то время, когда Вы объявляете их, иначе их начальные значения будут содержать мусор от предыдущего содержания штабеля:
BOOL success = NO; |
int magicNumber = 42; |
Это не необходимо для объектных указателей, потому что компилятор автоматически установит переменную в nil
если Вы не указываете никакое другое начальное значение:
XYZPerson *somePerson; |
// somePerson is automatically set to nil |
A nil
значение является самым безопасным способом инициализировать объектный указатель, если у Вас нет другого значения для использования, потому что совершенно приемлемо в Objective C отправить сообщение в nil
. Если Вы действительно отправляете сообщение в nil
, очевидно, ничто не происходит.
Если необходимо проверить, чтобы удостовериться, что объект не nil
(на который переменная указывает на объект в памяти), можно или использовать стандарт C оператор неравенства:
if (somePerson != nil) { |
// somePerson points to an object |
} |
или просто предоставьте переменную:
if (somePerson) { |
// somePerson points to an object |
} |
Если somePerson
переменная nil
, его логическое значение 0
(ложь). Если это имеет адрес, это не нуль, поэтому оценивает как истина.
Точно так же, если необходимо проверить на a nil
переменная, можно или использовать оператор равенства:
if (somePerson == nil) { |
// somePerson does not point to an object |
} |
или просто используйте логического оператора отрицания C:
if (!somePerson) { |
// somePerson does not point to an object |
} |
Упражнения
Откройтесь
main.m
файл в Вашем проекте от упражнений в конце последней главы и находитmain()
функция. Как с любой исполнимой программой, записанной в C, эта функция представляет начальную точку для Вашего приложения.Создайте новое
XYZPerson
использование экземпляраalloc
иinit
, и затем вызовитеsayHello
метод.Реализуйте
saySomething:
метод, показанный ранее в этой главе и перезаписиsayHello
метод для использования его. Добавьте множество других поздравлений и вызовите каждого из них на экземпляре, который Вы создали выше.Создайте новые файлы класса для
XYZShoutingPerson
класс, набор для наследования отXYZPerson
.Переопределите
saySomething:
метод, чтобы вывести на экран прописное приветствие и протестировать поведение наXYZShoutingPerson
экземпляр.Реализуйте
XYZPerson
классperson
метод фабрики Вы объявили в предыдущей главе, для возврата правильно выделенного и инициализированного экземпляраXYZPerson
класс, затем используйте метод вmain()
вместо Вашего вложенногоalloc
иinit
.Создайте новую локальную переменную
XYZPerson
указатель, но не включают присвоения значения.Используйте ответвление (
if
оператор), чтобы проверить, присваивается ли переменная автоматически какnil
.