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 Method for 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 = @{@"магія": 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
Не зміг домогтися якого-небудь результату, вибравши цю опцію. У поясненні передбачається, що будуть додані анотації до властивостей і методів, але у мене не вийшло визначити, які атрибути будуть додані і при яких умовах. Залиште коментар, якщо знаєте…

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.