Принятие современного Objective C
За эти годы язык Objective C рос и развился. Несмотря на то, что базовые понятия и методы остаются тем же, части языка были через существенные изменения и улучшения. Эти модернизации улучшают безопасность типов, управление памятью, производительность и другие аспекты Objective C, упрощающего для Вас записать корректный код. Важно принять эти изменения в Вашем существующем и будущем коде, чтобы помочь ему стать более непротиворечивым, читаемым, и эластичным.
XCode обеспечивает инструмент, чтобы помочь внести некоторые из этих структурных изменений для Вас. Но перед использованием этого инструмента Вы хотите понять то, что изменяется, он предложит делать к Вашему коду, и почему. Этот документ выделяет некоторые старшие значащие и полезные модернизации для принятия в кодовой базе.
instancetype
Используйте instancetype
ключевое слово как тип возврата методов, возвращающих экземпляр класса, к ним обращаются (или подкласс того класса). Эти методы включают alloc
, init
, и методы фабрики классов.
Используя instancetype
вместо id
в надлежащих местах улучшает безопасность типов в Вашем коде Objective C. Например, рассмотрите следующий код:
@interface MyObject : NSObject |
+ (instancetype)factoryMethodA; |
+ (id)factoryMethodB; |
@end |
@implementation MyObject |
+ (instancetype)factoryMethodA { return [[[self class] alloc] init]; } |
+ (id)factoryMethodB { return [[[self class] alloc] init]; } |
@end |
void doSomething() { |
NSUInteger x, y; |
x = [[MyObject factoryMethodA] count]; // Return type of +factoryMethodA is taken to be "MyObject *" |
y = [[MyObject factoryMethodB] count]; // Return type of +factoryMethodB is "id" |
} |
Из-за instancetype
возвратите тип +factoryMethodA
, тип того выражения сообщения MyObject *
. С тех пор MyObject
не имеет a -count
метод, компилятор дает предупреждение о x
строка:
main.m: ’MyObject’ may not respond to ‘count’ |
Однако из-за id
возврат вводит +factoryMethodB
, компилятор не может дать предупреждение о y
строка. Начиная с объекта типа id
может быть любой класс, и так как метод вызвал -count
существует где-нибудь на некотором классе, к компилятору, из которого возможно что возвращаемое значение +factoryMethodB
реализует метод.
Удостоверяться instancetype
методы фабрики имеют правильное поведение разделения на подклассы, убедиться использовать [self class]
при выделении класса вместо того, чтобы относиться непосредственно к имени класса. После этого соглашения гарантирует, что компилятор выведет типы подкласса правильно. Например, рассмотрите попытку сделать это с подклассом MyObject
от предыдущего примера:
@interface MyObjectSubclass : MyObject |
@end |
void doSomethingElse() { |
NSString *aString = [MyObjectSubclass factoryMethodA]; |
} |
Компилятор дает соблюдающее предупреждение об этом коде:
main.m: Incompatible pointer types initializing ’NSString *’ with an expression of type ’MyObjectSubclass *’ |
В примере, +factoryMethodA
сообщение отправляет возвратам объект типа MyObjectSubclass
, который является типом получателя. Компилятор соответственно решает что тип возврата +factoryMethodA
должен иметь подкласс MyObjectSubclass
, не суперкласса, в котором был объявлен метод фабрики.
Как принять
В Вашем коде замените случаи id
как возвращаемое значение с instancetype
где это необходимо. Это обычно имеет место для init
методы и методы фабрики классов. Даже при том, что компилятор автоматически преобразовывает методы, начинающиеся с «выделения», «init», или «новый» и имеющие тип возврата id
возвратиться instancetype
, это не преобразовывает другие методы. Соглашение Objective C состоит в том, чтобы записать instancetype
явно для всех методов.
Обратите внимание на то, что необходимо заменить id
с instancetype
для возвращаемых значений только, не в другом месте в Вашем коде. В отличие от этого id
, instancetype
ключевое слово может использоваться только в качестве типа результата в объявлении метода.
Например:
@interface MyObject |
- (id)myFactoryMethod; |
@end |
должен стать:
@interface MyObject |
- (instancetype)myFactoryMethod; |
@end |
Также можно использовать современный преобразователь Objective C в XCode для внесения этого изменения в код автоматически. Для получения дополнительной информации посмотрите Рефакторинг Вашего Кода Используя XCode.
Свойства
Свойство Objective-C является открытым или закрытым методом, объявленным с @property
синтаксис.
@property (readonly, getter=isBlue) BOOL blue; |
Свойства получают состояние объекта. Они отражают внутренние атрибуты объекта и отношения к другим объектам. Свойства обеспечивают безопасный, удобный способ взаимодействовать с этими атрибутами, не имея необходимость писать ряд пользовательских методов доступа (несмотря на то, что свойства действительно позволяют пользовательским методам get и методам set, при желании).
Используя свойства вместо переменных экземпляра в как можно большем количестве мест предоставляет много преимуществ:
Автосинтезируемые методы get и методы set. Когда Вы объявляете свойство методом get по умолчанию, и методы установщика создаются для Вас.
Лучшее заявление о намерениях ряда методов. Из-за соглашений о присвоении имен метода доступа ясно точно, что делают метод get и метод set.
Ключевые слова свойства, что явно выраженная дополнительная информация о поведении. Свойства обеспечивают потенциал для объявления атрибутов как
assign
(по сравнению сcopy
),weak
,atomic
(по сравнению сnonatomic
), и т.д.
Методы свойства следуют простому соглашению о присвоении имен. Метод get является именем свойства (например, date
), и метод set является именем свойства с префиксом набора, записанным в Camel-регистре (например, setDate
). Соглашение о присвоении имен для булево свойств состоит в том, чтобы объявить их с именованным методом get начиная со слова «,»:
@property (readonly, getter=isBlue) BOOL blue; |
В результате вся следующая работа:
if (color.blue) { } |
if (color.isBlue) { } |
if ([color isBlue]) { } |
При решении, что может быть свойством, имейте в виду, что следующее не является свойствами:
init
методcopy
метод,mutableCopy
методМетод фабрики классов
Метод, инициирующий действие и возвращающий a
BOOL
результатМетод, явно изменяющий внутреннее состояние как побочный эффект метода get
Кроме того, рассмотрите следующий ряд правил при идентификации потенциальных свойств в коде:
Свойство чтения/записи имеет два метода доступа. Метод set берет один параметр и ничего не возвращает, и метод get не берет параметров и возвращает одно значение. Если Вы преобразовываете этот набор методов в свойство, тегируете его с
readwrite
ключевое слово.Свойство только для чтения имеет единственный метод доступа, метода get, не берущего параметров и возвращающего одно значение. Если Вы преобразовываете этот метод в свойство, тегируете его с
readonly
ключевое слово.Метод get должен быть идемпотентом (если метода get вызывают дважды, вторые результаты вызова в том же результате как первое). Однако приемлемо для метода get вычислить результат каждый раз, когда это вызывают.
Как принять
Идентифицируйте ряд методов, квалифицирующих, чтобы быть преобразованными в свойство, такое как они:
- (NSColor *)backgroundColor; |
- (void)setBackgroundColor:(NSColor *)color; |
и объявите их использующий @property
синтаксис с надлежащим ключевым словом (ами):
@property (copy) NSColor *backgroundColor; |
Для получения информации о ключевых словах свойства и других соображениях, посмотрите Данные Инкапсуляции.
Также можно использовать современный преобразователь Objective C в XCode для внесения этого изменения в код автоматически. Для получения дополнительной информации посмотрите Рефакторинг Вашего Кода Используя XCode.
Макросы перечисления
NS_ENUM
и NS_OPTIONS
макросы обеспечивают краткий, простой способ определить перечисления и опции на языках на базе С. Они макросы улучшают завершение кода в XCode и явно указывают тип и размер Ваших перечислений и опций. Кроме того, этот синтаксис объявляет перечисления в пути, оцененном правильно более старыми компиляторами, и более новыми, которые могут интерпретировать базовую информацию о типе.
Используйте NS_ENUM
макрос для определения перечислений, ряд значений, которые являются взаимоисключающими:
typedef NS_ENUM(NSInteger, UITableViewCellStyle) { |
UITableViewCellStyleDefault, |
UITableViewCellStyleValue1, |
UITableViewCellStyleValue2, |
UITableViewCellStyleSubtitle |
}; |
NS_ENUM
макрос помогает определить и имя и тип перечисления, в этом случае названного UITableViewCellStyle
из типа NSInteger
. Тип для перечислений должен быть NSInteger
.
Используйте NS_OPTIONS
макрос для определения опций, ряд bitmasked значения, которые могут быть объединены вместе:
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { |
UIViewAutoresizingNone = 0, |
UIViewAutoresizingFlexibleLeftMargin = 1 << 0, |
UIViewAutoresizingFlexibleWidth = 1 << 1, |
UIViewAutoresizingFlexibleRightMargin = 1 << 2, |
UIViewAutoresizingFlexibleTopMargin = 1 << 3, |
UIViewAutoresizingFlexibleHeight = 1 << 4, |
UIViewAutoresizingFlexibleBottomMargin = 1 << 5 |
}; |
Как перечисления, NS_OPTIONS
макрос определяет и имя и тип. Однако тип для опций должен обычно быть NSUInteger
.
Как принять
Замените Ваш enum
объявления, такие как этот:
enum { |
UITableViewCellStyleDefault, |
UITableViewCellStyleValue1, |
UITableViewCellStyleValue2, |
UITableViewCellStyleSubtitle |
}; |
typedef NSInteger UITableViewCellStyle; |
с NS_ENUM
синтаксис:
typedef NS_ENUM(NSInteger, UITableViewCellStyle) { |
UITableViewCellStyleDefault, |
UITableViewCellStyleValue1, |
UITableViewCellStyleValue2, |
UITableViewCellStyleSubtitle |
}; |
Но когда Вы используете enum
определить битовую маску, такой как здесь:
enum { |
UIViewAutoresizingNone = 0, |
UIViewAutoresizingFlexibleLeftMargin = 1 << 0, |
UIViewAutoresizingFlexibleWidth = 1 << 1, |
UIViewAutoresizingFlexibleRightMargin = 1 << 2, |
UIViewAutoresizingFlexibleTopMargin = 1 << 3, |
UIViewAutoresizingFlexibleHeight = 1 << 4, |
UIViewAutoresizingFlexibleBottomMargin = 1 << 5 |
}; |
typedef NSUInteger UIViewAutoresizing; |
используйте NS_OPTIONS
макрос.
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { |
UIViewAutoresizingNone = 0, |
UIViewAutoresizingFlexibleLeftMargin = 1 << 0, |
UIViewAutoresizingFlexibleWidth = 1 << 1, |
UIViewAutoresizingFlexibleRightMargin = 1 << 2, |
UIViewAutoresizingFlexibleTopMargin = 1 << 3, |
UIViewAutoresizingFlexibleHeight = 1 << 4, |
UIViewAutoresizingFlexibleBottomMargin = 1 << 5 |
}; |
Также можно использовать современный преобразователь Objective C в XCode для внесения этого изменения в код автоматически. Для получения дополнительной информации посмотрите Рефакторинг Вашего Кода Используя XCode.
Объектная инициализация
В Objective C объектная инициализация основывается на понятии определяемого инициализатора, метод инициализатора, который ответственен за вызов одного из инициализаторов его суперкласса и затем инициализации его собственных переменных экземпляра. Инициализаторы, не определяющиеся инициализаторы, известны как инициализаторы удобства. Инициализаторы удобства обычно делегируют к другому инициализатору — в конечном счете завершению цепочки в определяемом инициализаторе — вместо того, чтобы выполнить инициализацию сами.
Определяемый образец инициализатора помогает гарантировать, чтобы наследованные инициализаторы должным образом инициализировали все переменные экземпляра. Подкласс, который должен выполнить нетривиальную инициализацию, должен переопределить все определяемые инициализаторы своего суперкласса, но это не должно переопределять инициализаторы удобства. Для получения дополнительной информации об инициализаторах, посмотрите Объектную Инициализацию.
Для разъяснения различия между определяемыми и определяемыми ясными инициализаторами можно добавить NS_DESIGNATED_INITIALIZER
макрос к любому методу в init
семья, обозначая его определяемый инициализатор. Используя этот макрос представляет несколько ограничений:
Реализация определяемого инициализатора должна объединить в цепочку к суперклассу
init
метод (с[super init...]
) это - определяемый инициализатор для суперкласса.Реализация инициализатора удобства (инициализатор, не отмеченный как определяемый инициализатор в классе, имеющем по крайней мере один инициализатор, отмеченный как определяемый инициализатор), должна делегировать к другому инициализатору (с
[self init...]
).Если класс обеспечивает один или несколько определяемые инициализаторы, он должен реализовать все определяемые инициализаторы его суперкласса.
Если какое-либо из этих ограничений нарушено, Вы получаете предупреждения от компилятора.
Если Вы используете NS_DESIGNATED_INITIALIZER
макрос в Вашем классе, необходимо отметить все определяемые инициализаторы с этим макросом. Все другие инициализаторы будут считаться инициализаторами удобства.
Как принять
Идентифицируйте определяемые инициализаторы в своих классах и тегируйте их с NS_DESIGNATED_INITIALIZER
макрос. Например:
- (instancetype)init NS_DESIGNATED_INITIALIZER; |
Automatic Reference Counting (ARC)
Automatic Reference Counting (ARC) является функцией компилятора, обеспечивающей автоматическое управление памятью объектов Objective C. Вместо того, что вы имели необходимость помнить, когда использовать retain
, release
, и autorelease
, ARC оценивает пожизненные требования Ваших объектов и автоматически вставляет надлежащее управление памятью, зовет Вас во время компиляции. Компилятор также генерирует надлежащий dealloc
методы для Вас.
Как принять
XCode обеспечивает инструмент, автоматизирующий механические детали преобразования ARC (такие как удаление retain
и release
вызовы), и помогает Вам устранить проблемы, которые мигрант не может обработать автоматически. Для использования инструмента миграции ARC выберите Edit> Refactor> Convert to Objective-C ARC. Инструмент миграции преобразовывает все файлы в проекте использовать ARC.
Для получения дополнительной информации посмотрите Переход к Информации о версии ARC.
Рефакторинг кода Используя XCode
XCode обеспечивает современный преобразователь Objective C, который может помочь Вам во время процесса модернизации. Несмотря на то, что преобразователь помогает с механикой идентификации и применения потенциальных модернизаций, это не интерпретирует семантику Вашего кода. Например, это не обнаружит что Ваш -toggle
метод является действием, влияющим на состояние Вашего объекта, и это ошибочно предложит модернизировать это действие, чтобы быть свойством. Удостоверьтесь, что вручную рассмотрели и подтвердили любые изменения, которые преобразователь предлагает вносить в Ваш код.
- Из ранее описанных модернизаций преобразователь предлагает:
Изменение
id
кinstancetype
в надлежащих местахИзменение
enum
кNS_ENUM
илиNS_OPTIONS
Обновление к
@property
синтаксис
- Помимо этих модернизаций, этот преобразователь рекомендует дополнительные изменения в Вашем коде, включая:
Преобразование в литералы, таким образом, оператор как
[NSNumber numberWithInt:3]
становится@3
.Используя преобразование в нижний индекс, таким образом, оператор как
[dictionary setObject:@3 forKey:key]
становитсяdictionary[key] = @3
.
Для использования современного преобразователя Objective C выберите Edit> Refactor> Convert to Modern Objective-C Syntax.