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

Better Brewing with...

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

Better Brewing with...

Talk from NSManchester July 2015

Avatar for Sam Meadley

Sam Meadley

July 06, 2015

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