Slide 1

Slide 1 text

EVERYONE IS AN API DESIGNER @johnsundell

Slide 2

Slide 2 text

API APP SDK Frameworks Standard Library

Slide 3

Slide 3 text

Models Controllers APP Views Managers Coordinators Factories Delegates Observers

Slide 4

Slide 4 text

4 API characteristics

Slide 5

Slide 5 text

Contained

Slide 6

Slide 6 text

class ProfileViewController: UIViewController { var headerView: ProfileHeaderView? var nameLabel: UILabel? var logOutButton: UIButton? }

Slide 7

Slide 7 text

class ProfileViewController: UIViewController { var headerView: ProfileHeaderView? var nameLabel: UILabel? var logOutButton: UIButton? } override func loadView() { super.loadView() let headerView = ProfileHeaderView() headerView.delegate = self self.headerView = headerView }

Slide 8

Slide 8 text

func userDidUnlockPremiumSubscription() { profileController.headerView = PremiumHeaderView() }

Slide 9

Slide 9 text

override func loadView() { super.loadView() let headerView = ProfileHeaderView() headerView.delegate = self self.headerView = headerView } func userDidUnlockPremiumSubscription() { profileController.headerView = PremiumHeaderView() }

Slide 10

Slide 10 text

“You can’t use the API like that!” !

Slide 11

Slide 11 text

class ProfileViewController: UIViewController { } var headerView: ProfileHeaderView? var nameLabel: UILabel? var logOutButton: UIButton?

Slide 12

Slide 12 text

class ProfileViewController: UIViewController { } var headerView: ProfileHeaderView? var nameLabel: UILabel? var logOutButton: UIButton? private private private

Slide 13

Slide 13 text

class ProfileViewController: UIViewController { } var headerView: ProfileHeaderView? var nameLabel: UILabel? var logOutButton: UIButton? private private private extension ProfileViewController { enum Theme { case standard case premium } func style(with theme: Theme) { headerView?.apply(theme) } }

Slide 14

Slide 14 text

class ListViewController: UIViewController { init(coreDataContext: NSManagedObjectContext) { ... } } class DetailViewController: UIViewController { init(coreDataContext: NSManagedObjectContext) { ... } } class ProfileViewController: UIViewController { init(coreDataContext: NSManagedObjectContext) { ... } }

Slide 15

Slide 15 text

protocol Database { func loadObjects(matching query: Query) -> [T] func loadObject(withID id: String) -> T? func save(_ object: T) }

Slide 16

Slide 16 text

class DatabaseMock: Database { ... } protocol Database { func loadObjects(matching query: Query) -> [T] func loadObject(withID id: String) -> T? func save(_ object: T) } extension NSManagedObjectContext: Database { ... } extension Realm: Database { ... }

Slide 17

Slide 17 text

class ListViewController: UIViewController { init(database: Database) { ... } }

Slide 18

Slide 18 text

Great APIs are contained… …they don’t expose their implementation details… …which removes confusion and reduces the risk of “invalid use”. Protocols are a great way to define contained APIs… …that can have their underlying implementation easily changed.

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Conversational

Slide 21

Slide 21 text

[string stringByAppendingString:]

Slide 22

Slide 22 text

[AVCaptureMovieFileOutput recordsVideoOrientationAndMirroring ChangesAsMetadataTrackForConnection:]

Slide 23

Slide 23 text

struct CATransform3D { init(m11: CGFloat, m12: CGFloat, m13: CGFloat, m14: CGFloat, m21: CGFloat, m22: CGFloat, m23: CGFloat, m24: CGFloat, m31: CGFloat, m32: CGFloat, m33: CGFloat, m34: CGFloat, m41: CGFloat, m42: CGFloat, m43: CGFloat, m44: CGFloat) }

Slide 24

Slide 24 text

" ℹ Verbosity Information

Slide 25

Slide 25 text

JSONSerialization.jsonObject( with: data, options: [.allowFragments] ) NSJSONSerialization.JSONObjectWithData( data, options: NSJSONReadingOptions.AllowFragments ) Swift 2 Swift 3

Slide 26

Slide 26 text

class ActionSheetViewController: UIViewController { func addButton(button: UIButton, dismiss: Bool) { ... } }

Slide 27

Slide 27 text

actionSheet.addButton(button: finishButton, dismiss: true) Button, button, button $ What is this dismissing? % class ActionSheetViewController: UIViewController { func addButton(button: UIButton, dismiss: Bool) { ... } }

Slide 28

Slide 28 text

actionSheet.add(button: finishButton, dismiss: true) class ActionSheetViewController: UIViewController { func add(button: UIButton, dismiss: Bool) { ... } }

Slide 29

Slide 29 text

actionSheet.add(dismissButton: finishButton) class ActionSheetViewController: UIViewController { func add(button: UIButton) { ... } } func add(dismissButton: UIButton) { ... }

Slide 30

Slide 30 text

Great APIs are conversational… …they make their intent clear by using readable declarations… …which eliminates cruft and makes usages more obvious. By defining separate APIs for separate actions & functionality… …we can make them easier to read, and harder to misunderstand.

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

Scalable

Slide 33

Slide 33 text

Wrap

Slide 34

Slide 34 text

extension User { func serialize() -> [String : Any] { return [ "name": name, "age": age, "hobbies": hobbies.map { $0.serialize() }, "address": address, "phoneNumber": phoneNumber ?? NSNull() ] } }

Slide 35

Slide 35 text

let data: Data = try wrap(object)

Slide 36

Slide 36 text

func wrap(_ object: T) throws -> Data

Slide 37

Slide 37 text

func wrap( _ object: T, keyConverter: KeyConverter, propertyOverrides: [String : Any], propertyNamesToIgnore: Set ) throws -> Data

Slide 38

Slide 38 text

let data: Data = try wrap(object)

Slide 39

Slide 39 text

func wrap( context: Any?, dateFormatter: DateFormatter? ) -> Any? } var wrapKeyStyle: WrapKeyStyle { get } func keyForWrapping( propertyNamed propertyName: String ) -> String? protocol WrapCustomizable {

Slide 40

Slide 40 text

func wrap( context: Any?, dateFormatter: DateFormatter? ) -> Any? } var wrapKeyStyle: WrapKeyStyle { get } func keyForWrapping( propertyNamed propertyName: String ) -> String? protocol WrapCustomizable {

Slide 41

Slide 41 text

public extension WrapCustomizable { } var wrapKeyStyle: WrapKeyStyle { return .matchPropertyName } func wrap( context: Any?, dateFormatter: DateFormatter? ) -> Any? { ... } func keyForWrapping( propertyNamed propertyName: String ) -> String? { ... }

Slide 42

Slide 42 text

extension User: WrapCustomizable { var wrapKeyStyle: WrapKeyStyle { return .convertToSnakeCase } }

Slide 43

Slide 43 text

func wrap( _ object: T, context: Any? dateFormatter: DateFormatter? ) throws -> Data = nil, = nil

Slide 44

Slide 44 text

try wrap(object, context: context, dateFormatter: formatter) try wrap(object) try wrap(object, context: context)

Slide 45

Slide 45 text

Great APIs are scalable… …they are easy to start using… …yet flexible enough for more advanced use cases. This can be achieved using extensions & default arguments… …and by moving overrides from the call site to type definitions.

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

Evolving

Slide 48

Slide 48 text

func presentProfile(animated: Bool) { ... }

Slide 49

Slide 49 text

func presentProfile(animated: Bool, duration: TimeInterval, curve: UIViewAnimationCurve, showFriends: Bool, completionHandler: @escaping () -> Void) { ... } % &

Slide 50

Slide 50 text

struct Animation { var duration: TimeInterval var curve: UIViewAnimationCurve var completionHandler: () -> Void } class ProfileViewController: UIViewController { init(profile: Profile, showFriends: Bool) } func present(_ profileViewController: ProfileViewController, with animation: Animation? = nil) { ... }

Slide 51

Slide 51 text

Backwards compatibility ' '

Slide 52

Slide 52 text

func present(_ profileViewController: ProfileViewController, with animation: Animation? = nil) { ... } func presentProfile(animated: Bool, duration: TimeInterval, curve: UIViewAnimationCurve, showFriends: Bool, completionHandler: @escaping () -> Void) { @available(*, deprecated, renamed: “present(with:)") } ...

Slide 53

Slide 53 text

duration: TimeInterval, curve: UIViewAnimationCurve, showFriends: Bool, completionHandler: @escaping () -> Void) { } let viewController = ProfileViewController(profile: profile, showFriends: showFriends) let animation: Animation? if animated { animation = Animation(duration: duration, curve: curve, completionHandler: completionHandler) } else { animation = nil } present(viewController, with: animation)

Slide 54

Slide 54 text

Great APIs are constantly evolving… …rather than just growing in scope and complexity… …they are refactored and broken up into new ones. Backwards compatibility is a really beautiful thing… …and can be easily achieved using deprecations or additive changes.

Slide 55

Slide 55 text

Evolving Scalable Contained Conversational

Slide 56

Slide 56 text

@johnsundell swiftbysundell.com