Share
Embed
Transcript
34 slides
-
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. -
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; } -
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; @end NS_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; @end NS_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?) -
Adopting NS_DESIGNATED_INITIALIZER • 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_INITIALIZER • 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_INITIALIZER • 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