Уже довольно давно в Xсode есть возможность проверить свой код на соответствие современным особенностям Objective-C (Edit > Refactor > Convert to Modern Objective-C Syntax…). Мне всегда было интересно наблюдать за тем, что Apple продвигает в качестве хорошей практики; и даже если вы не доверяете Xcode автоматически изменять код, это простой способ проверить его на возможность внесения потенциальных улучшений.
Xcode 6 представляет несколько нововведений, а кроме того, гораздо большую гибкость, позволяя самостоятельно контролировать, какие преобразования запускать:

К сожалению, из описания преобразования не всегда очевидно, что оно делает. Некоторые полезные подробности можно прочитать в руководстве Adopting Modern Objective-C, а также посмотреть на WWDC 2014 Session 417 What’s New in LLVM. Эта статья содержит мои заметки по каждому из преобразований.
Едва ли введение нового синтаксиса для свойств можно назвать новостью. Xcode 6 расширил список нововведений, добавив две опции для обнаружения свойств вместе с контролем их атомарности.
При выборе этих опций будет осуществлён поиск недостающего объявления
Xcode сделает вывод о необходимости свойства и добавит его в интерфейс класса:
Объявление свойства явно показывает назначение двух методов и позволяет компилятору автоматически синтезировать акцессор. Тут следует быть осторожным, потому что два существующих метода не будут автоматически удалены. Если в них описано нестандартное поведение, то это может быть опасным. Кроме того, Xcode может перестараться и предложить свойство для методов, которые не являются getter или setter методами, что делает это преобразование менее полезным.
Эта опция позволяет вам выбрать, хотите ли вы, чтобы при создании объявления только что обнаруженного свойства, оно было
Преобразование Infer designated initializer methods идентифицирует и помечает назначенные конструкторы с помощью
Метод инициализации отвечает как за установку значения для любой instance-переменной, так и за все остальные начальные задачи для объекта. У класса может быть много методов инициализации, которые по соглашению начинаются с префикса
Простой init в этом случае — удобный (convenience) конструктор, которой просто вызывает назначенный (designated) конструктор
Если же теперь у этого класса появится наследник, то наследнику станут важны детали реализации суперкласса. Правила для назначенных конструкторов:
Долгое время не было способа показать компилятору или тому, кто использует класс, какой из методов инициализации является назначенным (кроме как в комментарии). Теперь, чтобы исправить эту ситуацию, в Clang есть атрибут
Рассмотренный ранее пример в этом случае можно записать так:
Теперь, если вы добавили удобный конструктор, который не вызывает назначенный конструктор, то получите предупреждение.
Я видел, что люди сообщали о проблемах с некоторыми классами UIKit, в которых Apple ещё не пометила назначенные конструкторы, так что, как обычно, не помешает провести тестирование и отправить отчёт об ошибке в случае непредвиденных результатов.
Позволяет заменить
Это преобразование, отключённое по умолчанию, позволяет Xcode добавить отсутствующее объявление поддержки протокола. К примеру, вот простой контроллер, не заявляющий о поддержке какого-либо протокола:
Если этот класс реализует два обязательных метода для протокола UITableViewDataSource
От себя могу добавить, что так происходит только в том случае, если реализованы все обязательные методы. У меня не получилось добиться, например, добавления поддержки протокола UITableViewDelegate, если были реализованы только опциональные методы.
Литералы и индексирование Objective-C уже были представлены ранее в Xcode, так что я просто приведу быстрый пример:
Рефакторинг с использованием литералов и индексирования Objective-C приводит к более компактному коду:
Макросы
Современные макросы
после рефакторинга с использованием
Подобным образом набор битовых масок:
после рефакторинга с использованием
Не смог добиться какого-либо результата, выбрав эту опцию. В пояснении предполагается, что будут добавлены аннотации к свойствам и методам, но у меня не получилось определить, какие атрибуты будут добавлены и при каких условиях. Оставьте комментарий, если знаете…
Оригинал. Автор: Keith Harrison.
Xcode 6 представляет несколько нововведений, а кроме того, гораздо большую гибкость, позволяя самостоятельно контролировать, какие преобразования запускать:

К сожалению, из описания преобразования не всегда очевидно, что оно делает. Некоторые полезные подробности можно прочитать в руководстве Adopting Modern Objective-C, а также посмотреть на WWDC 2014 Session 417 What’s New in LLVM. Эта статья содержит мои заметки по каждому из преобразований.
Синтаксис @property
Едва ли введение нового синтаксиса для свойств можно назвать новостью. Xcode 6 расширил список нововведений, добавив две опции для обнаружения свойств вместе с контролем их атомарности.
- Infer readonly properties (по умолчанию Yes)
- Infer readwrite properties (по умолчанию Yes)
При выборе этих опций будет осуществлён поиск недостающего объявления
@property
путём определения потенциальных getter и setter методов в классе. К примеру, для такого класса с двумя методами без соответствующего свойства:- (NSString *)name;
- (void)setName:(NSString *)newName;
Xcode сделает вывод о необходимости свойства и добавит его в интерфейс класса:
@property (nonatomic, copy) NSString *name;
Объявление свойства явно показывает назначение двух методов и позволяет компилятору автоматически синтезировать акцессор. Тут следует быть осторожным, потому что два существующих метода не будут автоматически удалены. Если в них описано нестандартное поведение, то это может быть опасным. Кроме того, Xcode может перестараться и предложить свойство для методов, которые не являются getter или setter методами, что делает это преобразование менее полезным.
- Atomicity of inferred properties (по умолчанию NS_NONATOMIC_IOSONLY)
Эта опция позволяет вам выбрать, хотите ли вы, чтобы при создании объявления только что обнаруженного свойства, оно было
atomic
, nonatomic
или был использован макрос NS_NONATOMIC_IOSONLY
. Последнее есть ни что иное, как макрос, который принимает значение nonatomic
для iOS и ничего не делает для OS X. Если вы пишете код для обоих систем, это вам пригодится. Иначе, в большинстве случаев, стоит остановиться на nonatomic
.@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSDate *dueDate;
Назначенный конструктор (designated Initializer)
Преобразование Infer designated initializer methods идентифицирует и помечает назначенные конструкторы с помощью
NS_DESIGNATED_INITIALIZER
. Чтобы понять, что это такое и для чего это нужно, стоит вкратце вспомнить, как работает инициализация объекта в Objective-C. Создание объекта в Objective-C проходит в два шага: выделение памяти, а затем инициализация. Обычно их записывают в одну строку:MyObject *object = [[MyObject alloc] init];
Метод инициализации отвечает как за установку значения для любой instance-переменной, так и за все остальные начальные задачи для объекта. У класса может быть много методов инициализации, которые по соглашению начинаются с префикса
init
. Например, у класса с instance-переменной name
всегда должна быть определена, может быть метод инициализации, который включает в себя имя:- (instancetype)init
{
return [self initWithName:@"Unknown"];
}
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self)
{
_name = [name copy];
}
return self;
}
Простой init в этом случае — удобный (convenience) конструктор, которой просто вызывает назначенный (designated) конструктор
initWithName:
, используя в качестве параметра значение по умолчанию. Назначенный метод гарантирует полную инициализацию объекта, вызывая унаследованный init
.Если же теперь у этого класса появится наследник, то наследнику станут важны детали реализации суперкласса. Правила для назначенных конструкторов:
- назначенный конструктор. должен вызывать (через super) назначенный конструктор суперкласса. Для класса, наследующегося от NSObject, это будет просто
[super init]
. - Любой удобный конструктор должен вызывать другой конструктор в своём классе, который в конце-концов приведёт к назначенному конструктору.
- Класс с назначенным конструктором должен реализовывать все назначенные конструкторы суперкласса.
Долгое время не было способа показать компилятору или тому, кто использует класс, какой из методов инициализации является назначенным (кроме как в комментарии). Теперь, чтобы исправить эту ситуацию, в Clang есть атрибут
objc_designated_initializer
. А в iOS 8 есть макрос NS_DESIGNATED_INITIALIZER
, определённый в NSObjCRuntime.h, который позволяет легче применить этот атрибут к методу:#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
Рассмотренный ранее пример в этом случае можно записать так:
- (instancetype)init;
- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER;
Теперь, если вы добавили удобный конструктор, который не вызывает назначенный конструктор, то получите предупреждение.
Я видел, что люди сообщали о проблемах с некоторыми классами UIKit, в которых Apple ещё не пометила назначенные конструкторы, так что, как обычно, не помешает провести тестирование и отправить отчёт об ошибке в случае непредвиденных результатов.
Infer Instancetype for Method Result Type
Позволяет заменить
id
на instancetype
в качестве возвращаемого типа для методов alloc
, init
и new
. Factory-методы класса возможно вам придётся изменять вручную. Более детально о том, как повысить надёжность кода, применяя instancetype
, можно прочитать в статье NSHipster.Infer Protocol Conformance
Это преобразование, отключённое по умолчанию, позволяет Xcode добавить отсутствующее объявление поддержки протокола. К примеру, вот простой контроллер, не заявляющий о поддержке какого-либо протокола:
@interface UYLViewController : UIViewController
Если этот класс реализует два обязательных метода для протокола UITableViewDataSource
-tableView:numberOfRowsInSection:
и -tableView:cellForRowAtIndexPath:
, то описание интерфейса будет изменено следующим образом:@interface UYLViewController : UIViewController<UITableViewDataSource>
От себя могу добавить, что так происходит только в том случае, если реализованы все обязательные методы. У меня не получилось добиться, например, добавления поддержки протокола UITableViewDelegate, если были реализованы только опциональные методы.
Литералы Objective-C
Литералы и индексирование Objective-C уже были представлены ранее в Xcode, так что я просто приведу быстрый пример:
NSNumber *magicNumber = [NSNumber numberWithInteger:42];
NSDictionary *myDictionary = [NSDictionary dictionaryWithObject:magicNumber forKey:@"magic"];
Рефакторинг с использованием литералов и индексирования Objective-C приводит к более компактному коду:
NSNumber *magicNumber = @42;
NSDictionary *myDictionary = @{@"magic": magicNumber};
Перечисления
Макросы
NS_ENUM
и NS_OPTIONS
.Современные макросы
NS_ENUM
и NS_OPTIONS
— это быстрый и лёгкий способ создания перечислений с указанием типа и размера компилятору. Например, перечисляемый тип:enum
{
UYLTypeDefault,
UYLTypeSmall,
UYLTypeLarge
};
typedef NSInteger UYLType;
после рефакторинга с использованием
NS_ENUM
превращается в:typedef NS_ENUM(NSInteger, UYLType)
{
UYLTypeDefault,
UYLTypeSmall,
UYLTypeLarge
};
Подобным образом набор битовых масок:
enum
{
UYLBitMaskA = 0,
UYLBitMaskB = 1 << 0,
UYLBitMaskC = 1 << 1,
UYLBitMaskD = 1 << 2
};
typedef NSUInteger UYLBitMask;
после рефакторинга с использованием
NS_OPTIONS
превращается в:typedef NS_OPTIONS(NSUInteger, UYLBitMask)
{
UYLBitMaskA = 0,
UYLBitMaskB = 1 << 0,
UYLBitMaskC = 1 << 1,
UYLBitMaskD = 1 << 2
};
Add attribute annotations
Не смог добиться какого-либо результата, выбрав эту опцию. В пояснении предполагается, что будут добавлены аннотации к свойствам и методам, но у меня не получилось определить, какие атрибуты будут добавлены и при каких условиях. Оставьте комментарий, если знаете…
Оригинал. Автор: Keith Harrison.