Xcode 6 Objective-C Modernization

Xcode has for a long time included a refactoring tool which will check your code for modern Objective-C features (Edit > Refactor > Convert to Modern Objective-C Syntax…). I always find it interesting to see what Apple is promoting as good practise and even if you don’t trust Xcode to automatically refactor it is a simple way to audit code for potential improvement.

Xcode 6 introduces a few new modernizations but also a lot more flexibility by allowing individual control other which conversions to run:

Unfortunately it is not always obvious from the description what each conversion does. There are some useful details in the Adopting Modern Objective-C guide along with a demo in the WWDC 2014 Session 417 What’s New in LLVM. This post collects my notes on each of the conversions.

@Property Syntax

The introduction of the newer property syntax is hardly news. Xcode 6 has extended the modernization by including two new conversions to infer properties together with a control for the atomicity of the inferred properties.

  • Infer readonly properties (default Yes)
  • Infer readwrite properties (default Yes)

These first two conversions look for missing @property declarations by identifying potential getter and setter methods in a class. For example, for a class with the two methods but no corresponding property.

- (NSString *)name;
- (void)setName:(NSString *)newName;

Xcode will infer the property which it will add to the class interface:

@property (nonatomic, copy) NSString *name;

The property declaration makes the intent of the two methods explicit and allows the compiler to automatically synthesise the accessor methods. Be aware that the modernization tool will not remove the existing methods for you. That would potentially be dangerous if you have custom behaviour. This conversion can also get carried away and propose properties for methods that are not getters or setters which makes it less useful.

  • Atomicity of inferred properties (default NS_NONATOMIC_IOSONLY)

When creating the inferred property declaration this setting allows you to choose whether you want the property to be atomic, nonatomic or to use the macro NS_NONATOMIC_IOSONLY. The latter is a macro which evaluates to nonatomic on iOS and to nothing on OS X. It would be a good choice for code that you expect to build on both iOS and OS X. For iOS only code you can stick with nonatomic.

@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSDate *dueDate;

Designated Initializers

The Infer designated initializer methods conversion identifies and tags the designated initializers of a class with NS_DESIGNATED_INITIALIZER. To understand why that is useful it is worth recapping how object initialization works in Objective-C. Creation of an Objective-C object is a two step process of allocation and then initialization usually written as a single line of code:

MyObject *object = [[MyObject alloc] init];

The initialization method takes care of setting values for any instance variables as well as any other setup tasks for the object. A class can have many initializer methods, by convention named with the prefix init. For example, a class with an instance variable name that must always be set may have an initializer that includes the name:

- (instancetype)init {
  return [self initWithName:@"Unknown"];
}

- (instancetype)initWithName:(NSString *)name {
  self = [super init];
  if (self) {
    _name = [name copy];
  }
  return self;
}

The plain init method is in this case a convenience initializer which simply calls the designated initializer initWithName: with a default value. The designated initializer guarantees the object is fully initialised by sending an initialization message to the superclass. The implementation detail becomes important to a user of the class when they subclass it. The rules for designated initializers in detail:

  • A designated initializer must call (via super) a designated initializer of the superclass. Where NSObject is the superclass this is just [super init].
  • Any convenience initializer must call another initializer in the class - which eventually leads to a designated initializer.
  • A class with designated initializers must implement all of the designated initializers of the superclass.

For a long time there was no way to tell the compiler or user of a class which are the designated initializers (other than in a comment). To correct that situation Clang now has the attribute objc_designated_initializer. In iOS 8 the NS_DESIGNATED_INITIALIZER macro, defined in NSObjCRuntime.h, makes it easy to apply this to a method:

#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))

So for the earlier example we would have:

- (instancetype)init;
- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER; 

If a convenience initializer you add does not call a designated initializer you will now get a warning. I have seen people reporting problems with some UIKit classes where Apple has not yet tagged designated initializers so as always you will want to test and file bug reports for unexpected results.

Infer Instancetype for Method Result Type

This migration will replace id with instancetype as the result type of “alloc”, “init” and “new” methods. You may need to manually convert any class factory methods. See this NSHipster article for details on the increased type safety you get from adopting instancetype.

Infer Protocol Conformance

This conversion which is off by default causes Xcode to add missing protocol conformance declarations. For example, a plain view controller which does not declare conformance to any protocols:

@interface UYLViewController : UIViewController 

If this class implements the two mandatory table view data source methods -tableView:numberOfRowsInSection: and -tableView:cellForRowAtIndexPath: it would have the interface statement modified as follows:

@interface UYLViewController : UIViewController<UITableViewDataSource> 

From what I can tell the protocol conformance is inferred if all of the mandatory methods are implemented. I could not for example make it infer compliance to the UITableViewDelegate protocol which has only optional methods.

Objective-C Literals

  • ObjC literals
  • ObjC subscripting

These two migrations were already present in Xcode 5 so I will just include a quick example:

NSNumber *magicNumber = [NSNumber numberWithInteger:42];
NSDictionary *myDictionary = [NSDictionary dictionaryWithObject:magicNumber forKey:@"magic"];

Refactoring to use Objective-C literals and subscripting results in the more compact code:

NSNumber *magicNumber = @42;
NSDictionary *myDictionary = @{@"magic": magicNumber};

Enumerations

  • Use NS_ENUM/NS_OPTIONS macros

The modern NS_ENUM and NS_OPTIONS macros are a quick and easy way to create enumerations that specify the type and size to the compiler. For example, an enumerated type:

enum {
  UYLTypeDefault,
  UYLTypeSmall,
  UYLTypeLarge
};
typedef NSInteger UYLType;

refactored to use NS_ENUM becomes:

typedef NS_ENUM(NSInteger, UYLType) {
  UYLTypeDefault,
  UYLTypeSmall,
  UYLTypeLarge
};

Likewise for a set of bitmasks defined as follows:

enum {
  UYLBitMaskA = 0,
  UYLBitMaskB = 1 << 0,
  UYLBitMaskC = 1 << 1,
  UYLBitMaskD = 1 << 2
};
typedef NSUInteger UYLBitMask;

refactored to use NS_OPTIONS becomes:

typedef NS_OPTIONS(NSUInteger, UYLBitMask) {
  UYLBitMaskA = 0,
  UYLBitMaskB = 1 << 0,
  UYLBitMaskC = 1 << 1,
  UYLBitMaskD = 1 << 2
};

Miscellaneous

  • Add attribute annotations

I was unable to make this migration do anything. The explanatory text suggests it will add attribute annotations to properties and methods but I was unable to discover which attributes it adds and under what situations. Leave a comment if you know…