Теперь, когда мы изучили, какие типы данных доступны, мы можем поговорить о том, как на самом деле использовать их продуктивно. Мы узнали, как объявлять свойства в Hello, Objective-C, но эта глава углубляется в нюансы открытых свойств и переменных экземпляра. Сначала мы кратко рассмотрим базовый синтаксис свойств и переменных экземпляра, а затем обсудим, как использовать атрибуты поведения для изменения методов доступа.
Объявление недвижимости
Свойства могут быть объявлены в интерфейсе с помощью @property
. В качестве краткого обзора давайте взглянем на файл Person.h
который мы создали в главе Hello, Objective-C:
1
2
3
4
5
6
7
|
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (copy) NSString *name;
@end
|
Это объявляет свойство с именем name
типа NSString
. Атрибут (copy)
сообщает среде выполнения, что делать, когда кто-то пытается установить значение name
. В этом случае он создает независимую копию значения вместо указания на существующий объект. Мы поговорим об этом подробнее в следующей главе, Управление памятью.
Реализация свойств
В Hello, Objective-C мы использовали директиву @synthesize
для автоматического создания методов получения и установки. Помните, что метод getter — это просто имя свойства, а метод setName
по умолчанию — setName
:
1
2
3
4
5
6
7
|
#import «Person.h»
@implementation Person
@synthesize name = _name;
@end
|
Но также возможно вручную создать методы доступа. Выполнение этого вручную помогает понять, что @property
и @synthesize
делают за кулисами.
Включенный пример кода: ManualProperty
Сначала добавьте новое свойство в интерфейс Person
:
1
2
|
@property (copy) NSString *name;
@property unsigned int age;
|
Обратите внимание, что мы сохраняем age
как примитивный тип данных (не указатель на объект), поэтому ему не требуется звездочка перед именем свойства. Вернувшись в Person.m
, определите методы доступа явно:
1
2
3
4
5
6
7
|
— (unsigned int)age {
return _age;
}
— (void)setAge:(unsigned int)age {
_age = age;
}
|
Это именно то, что @synthesize
сделал бы для нас, но теперь у нас есть возможность проверить значения до их назначения. Однако нам не хватает одной вещи: переменной экземпляра _age
. @synthesize
автоматически создал _name
ivar, что позволяет нам отказаться от свойства name
.
Переменные экземпляра
Переменные экземпляра, также известные как ivars, являются переменными, предназначенными для использования внутри класса. Они могут быть объявлены внутри фигурных скобок после директив @implementation
или @implementation
. Например, в Person.h
измените объявление интерфейса на следующее:
1
2
3
|
@interface Person {
unsigned int _age;
}
|
Это определяет переменную экземпляра с именем _age
, поэтому этот класс теперь должен успешно скомпилироваться. По умолчанию переменные экземпляра, объявленные в интерфейсе, защищены . Эквивалентное определение класса C # будет примерно таким:
1
2
3
|
class Person {
protected uint _age;
}
|
Модификаторы области Objective-C такие же, как и в C #: закрытые переменные доступны только для содержащего класса, защищенные переменные доступны для всех подклассов, а открытые переменные доступны для других объектов. Вы можете определить область действия переменных экземпляра с помощью @private
, @protected
и @public
внутри @interface
, как показано в следующем коде:
1
2
3
4
5
6
7
8
|
@interface Person : NSObject {
@private
NSString *_ssn;
@protected
unsigned int _age;
@public
NSString *job;
}
|
Публичные ивары на самом деле немного за пределами норм Objective-C. Класс с открытыми переменными действует скорее как структура C, чем как класс; вместо обычного синтаксиса обмена сообщениями вам нужно использовать оператор указателя ->
. Например:
1
2
3
4
|
Person *frank = [[Person alloc] init];
frank->job = @»Astronaut»;
NSLog(@»%@», frank->job);
// NOT: [frank job];
|
Однако в большинстве случаев вы захотите скрыть детали реализации, используя декларацию @property
вместо общедоступных переменных экземпляра. Кроме того, поскольку переменные экземпляра являются технически подробностями реализации, многие программисты предпочитают сохранять все переменные экземпляра закрытыми. С учетом этого, ивары, объявленные в @implementation
, по умолчанию являются закрытыми. Итак, если бы вы переместили объявление Person.m
в Person.m
вместо заголовка:
1
2
3
|
@implementation Person {
unsigned int _age;
}
|
_age
будет определен как закрытая переменная. Помните об этом при работе с переменными экземпляра в подклассах, так как разные значения по умолчанию для интерфейса и декларации реализации могут сбивать с толку новичков в Objective-C.
Настройка аксессоров
Но хватит о переменных экземпляра; давайте вернемся к свойствам. Методы доступа могут быть настроены с использованием нескольких атрибутов объявления свойств (например, (copy)
). Некоторые из наиболее важных атрибутов:
-
getter=getterName
— Настройте имя метода доступа геттера. Помните, что по умолчанию это просто имя свойства. -
setter=setterName
— настроить имя метода доступа метода установки. Помните, чтоset
значение по умолчанию, за которым следует имя свойства (например,setName
). -
readonly
— сделать свойство доступным только для чтения, то есть будет синтезирован только геттер. По умолчанию свойства доступны для чтения и записи. Это нельзя использовать с атрибутомsetter
. -
nonatomic
— указывает, что методы доступа не должны быть потокобезопасными. По умолчанию свойства являются атомарными, что означает, что Objective-C будет использовать блокировку / сохранение (описанную в следующей главе), чтобы вернуть полное значение из метода получения / установки. Обратите внимание, однако, что это не гарантирует целостность данных между потоками — просто, что методы получения и установки будут атомарными. Если вы не в многопоточной среде, неатомарные свойства намного быстрее.
Распространенным вариантом использования для настройки имен получателей является соглашение о булевых именах. Многим программистам нравится добавлять в булевы имена переменных. Это легко реализовать с помощью атрибута getter
:
1
|
@property (getter=isEmployed) BOOL employed;
|
Внутри класс может использовать employed
переменную, но другие объекты могут использовать isEmployed
setEmployed
isEmployed
и setEmployed
для взаимодействия с объектом:
1
2
3
4
5
6
7
8
|
Person *frank = [[Person alloc] init];
[frank setName:@»Frank»];
[frank setEmployed:YES];
if ([frank isEmployed]) {
NSLog(@»Frank is employed»);
} else {
NSLog(@»Frank is unemployed»);
}
|
Многие другие атрибуты свойств относятся к управлению памятью, что будет обсуждаться в следующем разделе. Также возможно применить несколько атрибутов к одному свойству, разделяя их запятыми:
1
|
@property (getter=isEmployed, readonly) BOOL employed;
|
Синтаксис точек
В дополнение к методам получения / установки, также возможно использование точечной нотации для доступа к объявленным свойствам. Для разработчиков на C # это должно быть гораздо более знакомым, чем синтаксис обмена сообщениями в Objective-C в квадратных скобках:
1
2
3
|
Person *frank = [[Person alloc] init];
frank.name = @»Frank»;
NSLog(@»%@», frank.name);
|
Обратите внимание, что это просто удобство — оно переводится непосредственно в методы получения / установки, описанные ранее. Точечная запись не может использоваться для методов экземпляра.
Резюме
Свойства являются неотъемлемой частью любого объектно-ориентированного языка программирования. Это данные, на которых работают методы. Директива @property
— это удобный способ настроить поведение свойства, но она не делает ничего, что нельзя сделать, вручную создав методы getter и setter.
В следующей главе мы подробно рассмотрим, как свойства хранятся в памяти, а также несколько новых атрибутов свойств для управления этим поведением. После этого мы углубимся в методы, которые завершают основные объектно-ориентированные инструменты Objective-C.
Этот урок представляет собой главу из Objective-C, лаконично , бесплатную электронную книгу от команды Syncfusion .