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

Swift Eye for the Stringly Typed API

Andyy Hope
September 02, 2016

Swift Eye for the Stringly Typed API

This was the talk I gave at try! Swift NYC on September 2nd, 2016.

Andyy Hope

September 02, 2016
Tweet

More Decks by Andyy Hope

Other Decks in Programming

Transcript

  1. Stringly Typed? "A riff on "strongly typed". Used to describe

    an implementation that needlessly relies on strings when programmer & refactor friendly options are available." — Internet
  2. Stringly Typed? "A riff on "strongly typed". Used to describe

    an implementation that needlessly relies on strings when programmer & refactor friendly options are available." — Internet // Stringly typed let image = UIImage(named: "PalletTown") // Strongly typed let tableView = UITableView(style: .Plain)
  3. Stringly typed • Typos let notification = "userAlertNotificaton" • Side

    effects let image = UIImage(named: "Charmandr") • Collisions let keyA = "isUserLoggedIn" let keyB = "isUserLoggedIn" keyA == keyB
  4. Stringly typed • Runtime crashes tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "CellIdentifer") ... let

    cell: UITableViewCell = tableView .dequeueReusableCellWithIdentifier("CelIdentifer")
  5. UserDefaults • Used to store small persistent pieces of information:

    • Settings • Arrays • Booleans • (AKA) Diet CoreData
  6. UserDefaults • Used to store small persistent pieces of information:

    • Settings • Arrays • Booleans • (AKA) Diet CoreData • Stringly typed APIs
  7. Swift 3: Set API Old .setBool(true, forKey: "isUserLoggedIn") .setInt(1, forKey:

    "pokeballCount") .setObject(pikachu, forKey: "pikachu") New .set(true, forKey: "isUserLoggedIn") .set(1, forKey: "pokeballCount") .set(pikachu, forKey: "pikachu")
  8. Fix: Constants // UserDefaults.standard.set(true, forKey: "isUserLoggedIn") // UserDefaults.standard.bool(forKey: "isUserLoggedIn") ...

    let isUserLoggedInKey = "isUserLoggedIn" UserDefaults.standard.set(true, forKey: isUserLoggedInKey) UserDefaults.standard.bool(forKey: isUserLoggedInKey)
  9. Fix: Unify struct Constants { struct Keys { // Account

    static let isUserLoggedIn = "isUserLoggedIn" // Onboarding ... } }
  10. Fix: Unify with context struct Constants { struct Keys {

    struct Account { static let isUserLoggedIn = "isUserLoggedIn" } } }
  11. Recap // Strings let key = "isUserLoggedIn" // Constant &

    Grouped let key = Constants.isUserLoggedIn // Context let key = Constants.Keys.Account.isUserLoggedIn // Safety let key = Constants.Keys.Account.isUserLoggedIn.rawValue
  12. tl;dw // Set // UserDefaults.standard // .set(true, forKey: Constants.Keys.Account.isUserLoggedIn.rawValue) UserDefaults.standard.set(true,

    forKey: .isUserLoggedIn) // Get // UserDefaults.standard // .bool(forKey: Constants.Keys.Account.isUserLoggedIn.rawValue) UserDefaults.standard.bool(forKey: .isUserLoggedIn)
  13. Protocol: BoolDefaultSettable extension BoolDefaultSettable where BoolKey.RawValue == String { //

    Setter func set(_ value: Bool, forKey key: BoolKey) { let key = key.rawValue UserDefaults.standard.set(value, forKey: key) } }
  14. Protocol: BoolDefaultSettable extension BoolDefaultSettable where BoolKey.RawValue == String { //

    Getter func bool(forKey key: BoolKey) -> Bool { let key = key.rawValue return UserDefaults.standard.bool(forKey: key) } }
  15. protocol BoolDefaultSettable { associatedtype BoolKey : RawRepresentable } extension BoolDefaultSettable

    where BoolKey.RawValue == String { func set(_ value: Bool, forKey key: BoolKey) { let key = key.rawValue UserDefaults.standard.set(value, forKey: key) } func bool(forKey key: BoolKey) -> Bool { let key = key.rawValue return UserDefaults.standard.bool(forKey: key) } }
  16. Protocol: DefaultSettable Family protocol IntegerDefaultSettable { ... } protocol DoubleDefaultSettable

    { ... } protocol FloatDefaultSettable { ... } protocol ObjectDefaultSettable { ... } protocol URLDefaultSettable { ... }
  17. UserDefaults extension extension UserDefaults : BoolDefaultSettable { enum BoolKey :

    String { case isUserLoggedIn } } ... UserDefaults.standard.set(true, forKey: .isUserLoggedIn) UserDefaults.standard.bool(forKey: .isUserLoggedIn)
  18. Account extension extension Account : BoolDefaultSettable { enum BoolKey :

    String { case isUserLoggedIn } } ... Account.set(true, forKey: .isUserLoggedIn) Account.bool(forKey: .isUserLoggedIn)
  19. Protocol: KeyNamespaceable protocol KeyNamespaceable { func namespaced<T: RawRepresentable>(_ key: T)

    -> String } extension KeyNamespaceable { func namespaced<T: RawRepresentable>(_ key: T) -> String { return "\(Self.self).\(key.rawValue)" } }
  20. protocol KeyNamespaceable { func namespaced<T: RawRepresentable>(_ key: T) -> String

    } extension KeyNamespaceable { func namespaced<T: RawRepresentable>(_ key: T) -> String { return "\(Self.self).\(key.rawValue)" } } // key: "UserDefaults.isUserLoggedIn"
  21. extension BoolDefaultSettable where BoolKey.RawValue == String { func set(_ value:

    Bool, forKey key: BoolKey) { let key = key.rawValue UserDefaults.standard.set(value, forKey: key) } func bool(forKey key: BoolKey) -> Bool { let key = key.rawValue return UserDefaults.standard.bool(forKey: key) } }
  22. extension BoolDefaultSettable where BoolKey.RawValue == String { func set(_ value:

    Bool, forKey key: BoolKey) { let key = namespaced(key) UserDefaults.standard.set(value, forKey: key) } func bool(forKey key: BoolKey) -> Bool { let key = namespaced(key) return UserDefaults.standard.bool(forKey: key) } }
  23. Constants.swift extension Account : BoolDefaultSettable { ... } extension Onboarding

    : BoolDefaultSettable { ... } struct Constants { ... }
  24. Constants.swift extension Account : BoolDefaultSettable { ... } extension Onboarding

    : BoolDefaultSettable { ... } struct Constants { ... } ... Account.set(true, forKey: .isUserLoggedIn) Account.bool(forKey: .isUserLoggedIn)
  25. Defaults.swift struct Defaults { struct Account : BoolDefaultSettable { ...

    } struct Onboarding : BoolDefaultSettable { ... } }
  26. Defaults.swift struct Defaults { struct Account : BoolDefaultSettable { ...

    } struct Onboarding : BoolDefaultSettable { ... } } ... Defaults.Account.set(true, forKey: .isUserLoggedIn) Defaults.Onboarding.set(true, forKey: .isUserOnboarded) Defaults.Account.bool(forKey: .isUserLoggedIn) Defaults.Onboarding.bool(forKey: .isUserOnboarded)
  27. So what have we learned? • Stringly typed API's are

    bad • Plenty of room for improvement
  28. So what have we learned? • Stringly typed API's are

    bad • Plenty of room for improvement • Grouping constants can assist uniformity
  29. So what have we learned? • Stringly typed API's are

    bad • Plenty of room for improvement • Grouping constants can assist uniformity • Namespacing gives context
  30. So what have we learned? • Stringly typed API's are

    bad • Plenty of room for improvement • Grouping constants can assist uniformity • Namespacing gives context • Protocols are (so) hot right now
  31. So what have we learned? • Stringly typed API's are

    bad • Plenty of room for improvement • Grouping constants can assist uniformity • Namespacing gives context • Protocols are (so) hot right now • Rethink how we interact with APIs