Xcode 6 Objective-C Modernization Tool

в 20:52, , рубрики: ios development, objective-c, xcode, Xcode 6, перевод, разработка под iOS

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

Xcode 6 представляет несколько нововведений, а кроме того, гораздо большую гибкость, позволяя самостоятельно контролировать, какие преобразования запускать:

Xcode 6 Objective-C Modernization Tool - 1

К сожалению, из описания преобразования не всегда очевидно, что оно делает. Некоторые полезные подробности можно прочитать в руководстве 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

Не смог добиться какого-либо результата, выбрав эту опцию. В пояснении предполагается, что будут добавлены аннотации к свойствам и методам, но у меня не получилось определить, какие атрибуты будут добавлены и при каких условиях. Оставьте комментарий, если знаете…

Автор: emankin

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js