[Из песочницы] Xcode 6 Objective-C Modernization Tool
Уже довольно давно в 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. Эта статья содержит мои заметки по каждому из преобразований.
Синтаксис @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
Литералы 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 Не смог добиться какого-либо результата, выбрав эту опцию. В пояснении предполагается, что будут добавлены аннотации к свойствам и методам, но у меня не получилось определить, какие атрибуты будут добавлены и при каких условиях. Оставьте комментарий, если знаете…