Slide 1

Slide 1 text

Closures In API design { @hpique }

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Agenda 4 Closures 4 @autoclosure 4 @noescape 4 Default values

Slide 5

Slide 5 text

Closures are self-contained blocks of functionality

Slide 6

Slide 6 text

Using closures func showButton() { UIView.animateWithDuration(0.5, animations: { self.button.alpha = 1 }, completion: { finished in if finished { self.button.enabled = true } }) }

Slide 7

Slide 7 text

Trailing closures func showButton() { UIView.animateWithDuration(0.5, animations: { self.button.alpha = 1 }) { finished in if finished { self.button.enabled = true } } }

Slide 8

Slide 8 text

Closures capture the constants and variables used within

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Before closures UIAlertView

Slide 11

Slide 11 text

func showQuestionAlert() { questionAlert = UIAlertView(title: "Question", message: "Pigeons are the new cats?", delegate: self, cancelButtonTitle: nil, otherButtonTitles: "No", "Coo!") questionAlert.show() }

Slide 12

Slide 12 text

// MARK: UIAlertViewDelegate func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) { if (alertView === questionAlert) { switch buttonIndex { ... } } } func alertView(alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) { if (alertView === questionAlert) { questionAlert = nil } }

Slide 13

Slide 13 text

After closures UIAlertController

Slide 14

Slide 14 text

func showQuestionAlert() { let controller = UIAlertController(title: "Question", message: "Pigeons are the new cats?", preferredStyle: .Alert) let no = UIAlertAction(title: "No", style: .Default) { _ in ... } controller.addAction(no) let yes = UIAlertAction(title: "Coo!", style: .Default) { _ in ... } controller.addAction(yes) self.presentViewController(controller, animated: true) { ... } }

Slide 15

Slide 15 text

Closures encourage code locality

Slide 16

Slide 16 text

Using closures in our APIs

Slide 17

Slide 17 text

@autoclosure

Slide 18

Slide 18 text

Inneficient and func getExpensiveBool() -> Bool { NSThread.sleepForTimeInterval(10.0) println("Birds!!!") return true } let result = and(false, getExpensiveBool()) > Birds!!!

Slide 19

Slide 19 text

Short-circuit with closures func and(left: Bool, getRight : () -> Bool) -> Bool { if !left { return false } return getRight() } let result = and(false, { return getExpensiveBool() }) >

Slide 20

Slide 20 text

Use @autoclosure for parameter values that might not be used

Slide 21

Slide 21 text

Adding @autoclosure func and(left: Bool, @autoclosure getRight: () -> Bool) -> Bool let result = and(false, getExpensiveBool()) >

Slide 22

Slide 22 text

@autoclosure in the Standard Library func &&(lhs: T, @autoclosure rhs: () -> Bool) -> Bool func assert(@autoclosure condition: () -> Bool, _ message: @autoclosure () -> String = default, file: StaticString = default, line: UWord = default)

Slide 23

Slide 23 text

@noescape

Slide 24

Slide 24 text

Loop with closure func loop(duration: NSTimeInterval, reverse: Bool, animations: () -> Void)

Slide 25

Slide 25 text

Using loop class MYView: UIView { func animate() { loop(duration: 0.5, reverse: true) { self.scale(1.25) } } func scale(scale: Double) { ... } }

Slide 26

Slide 26 text

How long are we capturing self?

Slide 27

Slide 27 text

What does loop do? loop(duration: 0.5, reverse: true) { self.scale(1.25) } 4 Call animations on every cycle? 4 Call once, take snapshots and animate between them?

Slide 28

Slide 28 text

Use @noescape for closure parameters that will not outlive the function call

Slide 29

Slide 29 text

Adding @noescape func loop(duration: NSTimeInterval, reverse: Bool, @noescape animations: () -> Void)

Slide 30

Slide 30 text

No need for self class MYView: UIView { func doAnimations() { loop(duration: 0.5, reverse: true) { scale(1.25) } } func scale(scale: Double) { ... } }

Slide 31

Slide 31 text

@noescape in the Standard Library func reduce(initial: U, combine: @noescape (U, Self.Generator.Element) -> U) -> U

Slide 32

Slide 32 text

@autoclosure implies @noescape

Slide 33

Slide 33 text

Forward compatibility 4 Removing @noescape might break user code 4 Only add if sure that the function will never call the closure asynchronously

Slide 34

Slide 34 text

Default values

Slide 35

Slide 35 text

Event callbacks in objective-C - (void)fetchImageWithSuccess:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;

Slide 36

Slide 36 text

Swift translation func fetchImage(success: (UIImage -> Void)?, failure: (NSError -> Void)?) fetchImage(success: { image -> Void in save(image) }, failure: nil)

Slide 37

Slide 37 text

nil as default func fetchImage(success: (UIImage -> Void)? = nil, failure: (NSError -> Void)? = nil) { ... if let success = success { success(image) } } fetchImage(success: { image -> Void in save(image) })

Slide 38

Slide 38 text

No optionals func fetchImage(success: UIImage -> Void, failure: NSError -> Void) fetchImage(success: { image -> Void in save(image) }, failure: { _ in })

Slide 39

Slide 39 text

Empty closure as default func fetchImage(success: UIImage -> Void = { _ in }, failure: NSError -> Void = { _ in }) { ... success(image) } fetchImage(success: { image -> Void in save(image) })

Slide 40

Slide 40 text

Global function as default func fetchImage(success: UIImage -> Void = noop failure: NSError? -> Void = noop)

Slide 41

Slide 41 text

Defining noop func noop() {} func noop(value: T) {}

Slide 42

Slide 42 text

Defining noop func noop() {} func noop(value: T) {} func noop() -> T? { return nil }

Slide 43

Slide 43 text

Defining noop func noop() {} func noop(value: T) {} func noop() -> T? { return nil } func noop(value: T) -> S? { return nil }

Slide 44

Slide 44 text

Trailing confusion fetchImage() { _ in // What closure is this? }

Slide 45

Slide 45 text

Consider the trailing closure when defining parameter order

Slide 46

Slide 46 text

Finally func fetchImage(failure: NSError -> Void = noop, success: UIImage -> Void = noop)

Slide 47

Slide 47 text

Damn you Xcode!

Slide 48

Slide 48 text

Damn you again Xcode!

Slide 49

Slide 49 text

Be mindful of how you API looks in code completion and Quick Documentation

Slide 50

Slide 50 text

Compensating with documentation /// Fetches an image. /// /// :param: failure Closure to be called on failure. Default: noop. /// :param: success Closure to be called on success. Default: noop. func fetchImage(failure: NSError -> Void = noop, success: UIImage -> Void = noop)

Slide 51

Slide 51 text

Closures encourage code locality Use @autoclosure for parameter values that might not be used Use @noescape for closure parameters that will not outlive the function call

Slide 52

Slide 52 text

Prefer no-op closures as default values for closure parameters Consider the trailing closure when defining parameter order Be mindful of how consumers will discover your APIs

Slide 53

Slide 53 text

Coming next fetchImage().onSuccess { image in save(image) }.onFailure { error in handle(error) }

Slide 54

Slide 54 text

BCPL: the origin of {} GET "libhdr" LET start() = VALOF $( writes("Hello, World!*n") RESULTIS 0 $) Created by Martin Richards in 1966.

Slide 55

Slide 55 text

Thank you! Questions?

Slide 56

Slide 56 text

Needs more Swift