Работа с объектами

Большинство работы в приложении 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 сообщение.

Рисунок 2-1  Основной процесс выполнения программы обмена сообщениями

Для указания получателя сообщения важно понять, как указатели используются для обращения к объектам в 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.

  Процесс выполнения программы рисунка 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.

  Процесс выполнения программы рисунка 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.

  Процесс выполнения программы рисунка 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.

  Вложение рисунка 2-5 выделение и сообщение init

Методы инициализатора могут взять параметры

Некоторые объекты должны быть инициализированы с требуемыми значениями. 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
    }

Упражнения

  1. Откройтесь main.m файл в Вашем проекте от упражнений в конце последней главы и находит main() функция. Как с любой исполнимой программой, записанной в C, эта функция представляет начальную точку для Вашего приложения.

    Создайте новое XYZPerson использование экземпляра alloc и init, и затем вызовите sayHello метод.

  2. Реализуйте saySomething: метод, показанный ранее в этой главе и перезаписи sayHello метод для использования его. Добавьте множество других поздравлений и вызовите каждого из них на экземпляре, который Вы создали выше.

  3. Создайте новые файлы класса для XYZShoutingPerson класс, набор для наследования от XYZPerson.

    Переопределите saySomething: метод, чтобы вывести на экран прописное приветствие и протестировать поведение на XYZShoutingPerson экземпляр.

  4. Реализуйте XYZPerson класс person метод фабрики Вы объявили в предыдущей главе, для возврата правильно выделенного и инициализированного экземпляра XYZPerson класс, затем используйте метод в main() вместо Вашего вложенного alloc и init.

  5. Создайте новую локальную переменную XYZPerson указатель, но не включают присвоения значения.

    Используйте ответвление (if оператор), чтобы проверить, присваивается ли переменная автоматически как nil.