- PVSM.RU - https://www.pvsm.ru -
Уже довольно давно в Xсode есть возможность проверить свой код на соответствие современным особенностям Objective-C (Edit > Refactor > Convert to Modern Objective-C Syntax…). Мне всегда было интересно наблюдать за тем, что Apple продвигает в качестве хорошей практики; и даже если вы не доверяете Xcode автоматически изменять код, это простой способ проверить его на возможность внесения потенциальных улучшений.
Xcode 6 представляет несколько нововведений, а кроме того, гораздо большую гибкость, позволяя самостоятельно контролировать, какие преобразования запускать:
К сожалению, из описания преобразования не всегда очевидно, что оно делает. Некоторые полезные подробности можно прочитать в руководстве Adopting Modern Objective-C [1] а также посмотреть на WWDC 2014 Session 417 What’s New in LLVM [2]. Эта статья содержит мои заметки по каждому из преобразований.
Едва ли введение нового синтаксиса для свойств можно назвать новостью. Xcode 6 расширил список нововведений, добавив две опции для обнаружения свойств вместе с контролем их атомарности.
При выборе этих опций будет осуществлён поиск недостающего объявления @property
путём определения потенциальных getter и setter методов в классе. К примеру, для такого класса с двумя методами без соответствующего свойства:
- (NSString *)name;
- (void)setName:(NSString *)newName;
Xcode сделает вывод о необходимости свойства и добавит его в интерфейс класса:
@property (nonatomic, copy) NSString *name;
Объявление свойства явно показывает назначение двух методов и позволяет компилятору автоматически синтезировать акцессор. Тут следует быть осторожным, потому что два существующих метода не будут автоматически удалены. Если в них описано нестандартное поведение, то это может быть опасным. Кроме того, Xcode может перестараться и предложить свойство для методов, которые не являются getter или setter методами, что делает это преобразование менее полезным.
Эта опция позволяет вам выбрать, хотите ли вы, чтобы при создании объявления только что обнаруженного свойства, оно было atomic
, nonatomic
или был использован макрос NS_NONATOMIC_IOSONLY
. Последнее есть ни что иное, как макрос, который принимает значение nonatomic
для iOS и ничего не делает для OS X. Если вы пишете код для обоих систем, это вам пригодится. Иначе, в большинстве случаев, стоит остановиться на nonatomic
.
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSDate *dueDate;
Преобразование 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 init]
.Долгое время не было способа показать компилятору или тому, кто использует класс, какой из методов инициализации является назначенным (кроме как в комментарии). Теперь, чтобы исправить эту ситуацию, в Clang [3]есть атрибут 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 ещё не пометила назначенные конструкторы, так что, как обычно, не помешает провести тестирование и отправить отчёт об ошибке в случае непредвиденных результатов.
Позволяет заменить id
на instancetype
в качестве возвращаемого типа для методов alloc
, init
и new
. Factory-методы класса возможно вам придётся изменять вручную. Более детально о том, как повысить надёжность кода, применяя instancetype
, можно прочитать в статье NSHipster [4].
Это преобразование, отключённое по умолчанию, позволяет Xcode добавить отсутствующее объявление поддержки протокола. К примеру, вот простой контроллер, не заявляющий о поддержке какого-либо протокола:
@interface UYLViewController : UIViewController
Если этот класс реализует два обязательных метода для протокола UITableViewDataSource -tableView:numberOfRowsInSection:
и -tableView:cellForRowAtIndexPath:
, то описание интерфейса будет изменено следующим образом:
@interface UYLViewController : UIViewController<UITableViewDataSource>
От себя могу добавить, что так происходит только в том случае, если реализованы все обязательные методы. У меня не получилось добиться, например, добавления поддержки протокола UITableViewDelegate, если были реализованы только опциональные методы.
Литералы и индексирование 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
};
Не смог добиться какого-либо результата, выбрав эту опцию. В пояснении предполагается, что будут добавлены аннотации к свойствам и методам, но у меня не получилось определить, какие атрибуты будут добавлены и при каких условиях. Оставьте комментарий, если знаете…
Автор: emankin
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/ios-development/80862
Ссылки в тексте:
[1] Adopting Modern Objective-C: https://developer.apple.com/library/ios/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html
[2] WWDC 2014 Session 417 What’s New in LLVM: https://developer.apple.com/videos/wwdc/2014/?include=417#417
[3] Clang : http://clang.llvm.org/
[4] статье NSHipster: http://nshipster.com/instancetype/
[5] Источник: http://habrahabr.ru/post/249029/
Нажмите здесь для печати.