Download - Better brewing
Better Brewing with…Sam Meadley @sammeadley
Overview• Macros
• NS_DESIGNATED_INITIALIZER
• NS_UNAVAILABLE
• NS_ASSUME_NONNULL_BEGIN/END
• Behaviour changes between Xcode 6 vs Xcode 7
The real overview• We’re going to start a brewery. Right here,
tonight.
Any brewers in the house?
Brewing beer; a primer
• 4 fundamental ingredients (experimentation encouraged)
• Water (let’s make this easier)
• Hops
• Malt
• Yeast
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
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];}
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)
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
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! 🍻
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
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
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
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
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
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];
😭
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
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
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
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.
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?
Audited Regions
• That’s a whole lot of nonnull
• Audited Regions can help
• NS_ASSUME_NONNULL_BEGIN
• NS_ASSUME_NONNULL_END
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
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
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];
Better Brewing with nonnull
• Communicate correct usage of your class
• More awesome compile-time help
• Less bugs! (you’d hope, right?)
Changes in Xcode 7 (beta)
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
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]];}
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;}
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
Summary• https://openradar.appspot.com/21302875
• https://developer.apple.com/library/mac/documentation/General/Conceptual/CocoaEncyclopedia/Initialization/Initialization.html
• craftbeercraftcode.com
• https://github.com/sammeadley/brewery
Cheers! 🍻