33
Better Brewing with… Sam Meadley @sammeadley

Better brewing

Embed Size (px)

Citation preview

Page 1: Better brewing

Better Brewing with…Sam Meadley @sammeadley

Page 2: Better brewing

Overview• Macros

• NS_DESIGNATED_INITIALIZER

• NS_UNAVAILABLE

• NS_ASSUME_NONNULL_BEGIN/END

• Behaviour changes between Xcode 6 vs Xcode 7

Page 3: Better brewing

The real overview• We’re going to start a brewery. Right here,

tonight.

Page 4: Better brewing

Any brewers in the house?

Page 5: Better brewing

Brewing beer; a primer

• 4 fundamental ingredients (experimentation encouraged)

• Water (let’s make this easier)

• Hops

• Malt

• Yeast

Page 6: Better brewing

Let’s start a brewery

@interface CBCBrewComponents : NSObject

@property (copy, nonatomic) NSString *identifier;@property (copy, nonatomic) NSString *hopVariety;@property (copy, nonatomic) NSString *maltVariety;@property (copy, nonatomic) NSString *yeastStrain;

- (instancetype)initWithIdentifier:(NSString *)identifier hopVariety:(NSString *)hopVariety maltVariety:(NSString *)maltVariety yeastStrain:(NSString *)yeastStrain NS_DESIGNATED_INITIALIZER;

@end

Page 7: Better brewing

NS_DESIGNATED_INITIALIZER

• Formalises object initialisation- (instancetype)initWithIdentifier:(NSString *)identifier hopVariety:(NSString *)hopVariety maltVariety:(NSString *)maltVariety yeastStrain:(NSString *)yeastStrain{ self = [super init]; if (self) { _identifier = [identifier copy]; _hopVariety = [hopVariety copy]; _maltVariety = [maltVariety copy]; _yeastStrain = [yeastStrain copy]; } return self;}

- (instancetype)initWithHopVariety:(NSString *)hopVariety maltVariety:(NSString *)maltVariety yeastStrain:(NSString *)yeastStrain{ NSUUID *UUID = [NSUUID UUID]; return [self initWithIdentifier:UUID.UUIDString hopVariety:hopVariety maltVariety:maltVariety yeastStrain:yeastStrain];}

Page 8: Better brewing

NS_DESIGNATED_INITIALIZER

• Formalises object initialisation

• Generates warnings if convenience initialisers do not call the designated initialiser

• Communicates intent to other developers

• Even more warnings if you don’t override the designated initialiser inherited from superclass (more on that later)

Page 9: Better brewing

Well that was easy…• Let’s down tools, it’s brew time.

@interface CBCBrewDay : NSObject

- (instancetype)initWithDate:(NSDate *)date NS_DESIGNATED_INITIALIZER;

- (CBCBrew *)brewFromComponents:(CBCBrewComponents *)brewComponents;

@end

Page 10: Better brewing

Let’s start with a Pale Ale

CBCBrewDay *brewDay = [[CBCBrewDay alloc] initWithDate:[NSDate date]];

CBCBrewComponents *brewComponents = [[CBCBrewComponents alloc] init];brewComponents.identifier = @"Pale Ale";brewComponents.hopVariety = @"Fuggles";brewComponents.maltVariety = @"Maris Otter";brewComponents.yeastStrain = @“WLP002";

CBCBrew *brew = [brewDay brewFromComponents:brewComponents];

Nailed it… Cheers! 🍻

Page 11: Better brewing

Second time around…

• First batch won’t make it past the brewery gates, better brew a second batch.

CBCBrewDay *brewDay = [[CBCBrewDay alloc] initWithDate:[NSDate date]];

CBCBrewComponents *brewComponents = [[CBCBrewComponents alloc] init];

CBCBrew *brew = [brewDay brewFromComponents:brewComponents];

Erm, something is wrong here

Page 12: Better brewing

Second time around…

CBCBrewDay *brewDay = [[CBCBrewDay alloc] initWithDate:[NSDate date]];

CBCBrewComponents *brewComponents = [[CBCBrewComponents alloc] init];brewComponents.identifier = @"Pale Ale";brewComponents.hopVariety = @"Fuggles";brewComponents.maltVariety = @"Maris Otter";brewComponents.yeastStrain = @“WLP002";

CBCBrew *brew = [brewDay brewFromComponents:brewComponents];

Hell no, H20

Page 13: Better brewing

What went wrong?CBCBrewDay *brewDay = [[CBCBrewDay alloc] initWithDate:[NSDate date]];

CBCBrewComponents *brewComponents = [[CBCBrewComponents alloc] init];brewComponents.identifier = @"Pale Ale";brewComponents.hopVariety = @"Fuggles";brewComponents.maltVariety = @"Maris Otter";brewComponents.yeastStrain = @“WLP002";

CBCBrew *brew = [brewDay brewFromComponents:brewComponents];

• No error checking code in the brewFromComponents: method to ensure that all the required components are set

• Callers can initialise CBCBrewComponents using the default init inherited from NSObject

Page 14: Better brewing

Better Brewing with NS_UNAVAILABLE;

@interface CBCBrewComponents : NSObject

@property (copy, nonatomic) NSString *identifier;@property (copy, nonatomic) NSString *hopVariety;@property (copy, nonatomic) NSString *maltVariety;@property (copy, nonatomic) NSString *yeastStrain;

- (instancetype)initWithIdentifier:(NSString *)identifier hopVariety:(NSString *)hopVariety maltVariety:(NSString *)maltVariety yeastStrain:(NSString *)yeastStrain NS_DESIGNATED_INITIALIZER;

- (instancetype)init NS_UNAVAILABLE;

@end

Page 15: Better brewing

Better Brewing with NS_UNAVAILABLE;

CBCBrewDay *brewDay = [[CBCBrewDay alloc] initWithDate:[NSDate date]];

CBCBrewComponents *brewComponents = [[CBCBrewComponents alloc] initWithIdentifier:@"Pale Ale" hopVariety:@"Fuggles" maltVariety:@"Maris Otter" yeastStrain:@"WLP002"];CBCBrew *brew = [brewDay brewFromComponents:brewComponents];

• Communicate minimum valid object

• Compile time checking

• Earlier resolution, more beer for everyone

Page 16: Better brewing

The dining philosophers forgetful brewer problem

@interface CBCBrewComponents : NSObject

- (instancetype)initWithIdentifier:(NSString *)identifier hopVariety:(NSString *)hopVariety maltVariety:(NSString *)maltVariety yeastStrain:(NSString *)yeastStrain NS_DESIGNATED_INITIALIZER;

- (instancetype)init NS_UNAVAILABLE;

@end

CBCBrewDay *brewDay = [[CBCBrewDay alloc] initWithDate:[NSDate date]];

CBCBrewComponents *brewComponents = [[CBCBrewComponents alloc] initWithIdentifier:@"Pale Ale" hopVariety:nil maltVariety:@"Maris Otter" yeastStrain:@"WLP002"];CBCBrew *brew = [brewDay brewFromComponents:brewComponents];

😭

Page 17: Better brewing

Better Brewing with nonnull

• Nullability type specifiers introduced in Xcode 6.3

• Properties

• Parameters

• Method return values

• and more…

• nonnull and nullable

• NS_ASSUME_NONNULL_BEGIN/NS_ASSUME_NONNULL_END

Page 18: Better brewing

Better Brewing with nonnull

@interface CBCBrewComponents : NSObject

@property (copy, nonatomic, nonnull) NSString *identifier;@property (copy, nonatomic, nonnull) NSString *hopVariety;@property (copy, nonatomic, nonnull) NSString *maltVariety;@property (copy, nonatomic, nonnull) NSString *yeastStrain;

- (instancetype)initWithIdentifier:(nonnull NSString *)identifier hopVariety:(nonnull NSString *)hopVariety maltVariety:(nonnull NSString *)maltVariety yeastStrain:(nonnull NSString *)yeastStrain NS_DESIGNATED_INITIALIZER;

- (instancetype)init NS_UNAVAILABLE;

@end

Page 19: Better brewing

Better Brewing with nonnull

@interface CBCBrewComponents : NSObject

@property (copy, nonatomic, nonnull) NSString *identifier;@property (copy, nonatomic, nonnull) NSString *hopVariety;@property (copy, nonatomic, nonnull) NSString *maltVariety;@property (copy, nonatomic, nonnull) NSString *yeastStrain;

- (instancetype)initWithIdentifier:(nonnull NSString *)identifier hopVariety:(nonnull NSString *)hopVariety maltVariety:(nonnull NSString *)maltVariety yeastStrain:(nonnull NSString *)yeastStrain NS_DESIGNATED_INITIALIZER;

- (instancetype)init NS_UNAVAILABLE;

@end

Page 20: Better brewing

Better Brewing with nonnull

• Nullability type specifiers should not be applied partially.

• Along with properties and parameters, they can also applied to method return types.

• Xcode helps with this.

Page 21: Better brewing

Forgetful Brewer revisited

@interface CBCBrewComponents : NSObject

@property (copy, nonatomic, nonnull) NSString *identifier;@property (copy, nonatomic, nonnull) NSString *hopVariety;@property (copy, nonatomic, nonnull) NSString *maltVariety;@property (copy, nonatomic, nonnull) NSString *yeastStrain;

- (nonnull instancetype)initWithIdentifier:(nonnull NSString *)identifier hopVariety:(nonnull NSString *)hopVariety maltVariety:(nonnull NSString *)maltVariety yeastStrain:(nonnull NSString *)yeastStrain NS_DESIGNATED_INITIALIZER;

- (nonnull instancetype)init NS_UNAVAILABLE;

@end

Whaddayasay Xcode?

Page 22: Better brewing

Audited Regions

• That’s a whole lot of nonnull

• Audited Regions can help

• NS_ASSUME_NONNULL_BEGIN

• NS_ASSUME_NONNULL_END

Page 23: Better brewing

Audited Regions@interface CBCBrewComponents : NSObject

@property (copy, nonatomic, nonnull) NSString *identifier;@property (copy, nonatomic, nonnull) NSString *hopVariety;@property (copy, nonatomic, nonnull) NSString *maltVariety;@property (copy, nonatomic, nonnull) NSString *yeastStrain;

- (nonnull instancetype)initWithIdentifier:(nonnull NSString *)identifier hopVariety:(nonnull NSString *)hopVariety maltVariety:(nonnull NSString *)maltVariety yeastStrain:(nonnull NSString *)yeastStrain NS_DESIGNATED_INITIALIZER;

- (nonnull instancetype)init NS_UNAVAILABLE;

@end

Is equivalent to;NS_ASSUME_NONNULL_BEGIN@interface CBCBrewComponents : NSObject

@property (copy, nonatomic) NSString *identifier;@property (copy, nonatomic) NSString *hopVariety;@property (copy, nonatomic) NSString *maltVariety;@property (copy, nonatomic) NSString *yeastStrain;

- (instancetype)initWithIdentifier:(NSString *)identifier hopVariety:(NSString *)hopVariety maltVariety:(NSString *)maltVariety yeastStrain:(NSString *)yeastStrain NS_DESIGNATED_INITIALIZER;

- (instancetype)init NS_UNAVAILABLE;

@endNS_ASSUME_NONNULL_END

Page 24: Better brewing

Audited Regions

• You can mix and match nullability type specifier within an audited region.

NS_ASSUME_NONNULL_BEGIN@interface CBCBrewComponents : NSObject

@property (copy, nonatomic) NSString *identifier;@property (copy, nonatomic, nullable) NSString *hopVariety;@property (copy, nonatomic) NSString *maltVariety;@property (copy, nonatomic) NSString *yeastStrain;

- (instancetype)initWithIdentifier:(NSString *)identifier hopVariety:(NSString *)hopVariety maltVariety:(NSString *)maltVariety yeastStrain:(NSString *)yeastStrain NS_DESIGNATED_INITIALIZER;

- (instancetype)init NS_UNAVAILABLE;

@endNS_ASSUME_NONNULL_END

Page 25: Better brewing

Audited Regions• Even with all this help. Be sure to still

implement error checking. The compiler doesn’t catch it all.

• Be sure to check your inputs are valid.

CBCBrewDay *brewDay = [[CBCBrewDay alloc] initWithDate:[NSDate date]];

NSString *hopVariety = nil;CBCBrewComponents *brewComponents = [[CBCBrewComponents alloc] initWithIdentifier:@"Pale Ale" hopVariety:hopVariety maltVariety:@"Maris Otter" yeastStrain:@"WLP002"];

CBCBrew *brew = [brewDay brewFromComponents:brewComponents];

Page 26: Better brewing

Better Brewing with nonnull

• Communicate correct usage of your class

• More awesome compile-time help

• Less bugs! (you’d hope, right?)

Page 27: Better brewing

Changes in Xcode 7 (beta)

Page 28: Better brewing

Adopting NS_DESIGNATED_INITIALIZ

ER• Xcode 6.x

• add macro to designated initialiser

• Xcode 7 beta

• add macro to designated initialiser

• explicitly override superclass designated initialiser(s)

• … even if marked unavailable

Page 29: Better brewing

Adopting NS_DESIGNATED_INITIALIZ

ER• Override inherited initialiser with some

sensible defaults (not like these…)

- (instancetype)init{ return [self initWithIdentifier:[NSString string] hopVariety:[NSString string] maltVariety:[NSString string] yeastStrain:[NSString string]];}

Page 30: Better brewing

Adopting NS_DESIGNATED_INITIALIZ

ER• Even if superclass initialiser is marked NS_UNAVAILABLE, Xcode still issues warning

• https://openradar.appspot.com/21302875

• Call doesNotRecognizeSelector and return nil

- (instancetype)init{ [self doesNotRecognizeSelector:_cmd]; return nil;}

Page 31: Better brewing

Summary• Use NS_DESIGNATED_INITIALIZER to

formalise object initialisation

• Use NS_UNAVAILABLE to hide inherited members not relevant to your subclass

• Guard against nil inputs and communicate intent with NS_ASSUME_NONNULL_BEGIN/END

Page 33: Better brewing

Cheers! 🍻