Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Better Brewing with...

Better Brewing with...

Talk from NSManchester July 2015

Avatar for Sam Meadley

Sam Meadley

July 06, 2015
Tweet

More Decks by Sam Meadley

Other Decks in Programming

Transcript

  1. Overview • Macros • NS_DESIGNATED_INITIALIZER   • NS_UNAVAILABLE   •

    NS_ASSUME_NONNULL_BEGIN/END   • Behaviour changes between Xcode 6 vs Xcode 7
  2. The real overview • We’re going to start a brewery.

    Right here, tonight. 
 
 
 
 
 
 
 

  3. Brewing beer; a primer • 4 fundamental ingredients (experimentation encouraged)

    • Water (let’s make this easier) • Hops • Malt • Yeast
  4. 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
  5. 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; }
  6. 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]; }
  7. 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)
  8. 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
  9. 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!
  10. 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
  11. 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
  12. 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

  13. 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
  14. 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
  15. 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];
  16. 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
  17. 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
  18. 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
  19. 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.
  20. 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?
  21. Audited Regions • That’s a whole lot of nonnull •

    Audited Regions can help • NS_ASSUME_NONNULL_BEGIN   • NS_ASSUME_NONNULL_END
  22. 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
  23. 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
  24. 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];
  25. Better Brewing with nonnull • Communicate correct usage of your

    class • More awesome compile-time help • Less bugs! (you’d hope, right?)
  26. 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
  27. 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]]; }
  28. 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; }
  29. 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