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. SWIFT EYE FOR THE STRINGLY TYPED API Medium: Andyy Hope

    Twitter: @andyyhope
  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
  3. 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)
  4. Are there downsides to stringly typed APIs?

  5. Are there downsides to stringly typed APIs Yep

  6. Stringly typed • Typos let notification = "userAlertNotificaton" • Side

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

    cell: UITableViewCell = tableView .dequeueReusableCellWithIdentifier("CelIdentifer")
  8. Stringly typed • Unicode artifacts "Account.!isUserLoggedIn"

  9. Stringly typed • Disorderly • Title cased "IsUserLoggedIn" • Camel

    cased "isUserLoggedIn"
  10. Stringly typed • Disorderly • Prefixes "isUserLoggedIn" "Account.isUserLoggedIn" "com.andyyhope.Account.isUserLoggedIn"

  11. Stringly typed • Disorderly • Naming "isUserBlocked" "userBlocked" "hasUserBeenBlocked"

  12. They're everywhere... UIImage UITableView UICollectionView UIStoryboard UISegue URL DateFormatter NotificationCenter

    UserDefaults ...
  13. UserDefaults

  14. UserDefaults • Used to store small persistent pieces of information:

    • Settings • Arrays • Booleans
  15. None
  16. UserDefaults • Used to store small persistent pieces of information:

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

    • Settings • Arrays • Booleans • (AKA) Diet CoreData • Stringly typed APIs
  18. None
  19. Swift Eye for UserDefaults API

  20. Swift 3 UserDefaults Recap Xcode 8 Beta 6. August 15,

    2016
  21. Swift 3: Rename Old NSUserDefaults New UserDefaults

  22. Swift 3: Instance Old NSUserDefaults.standardUserDefaults() New UserDefaults.standard

  23. 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")
  24. Swift 3: Get API Old .boolForKey("isUserLoggedIn") .integerForKey("pokeballCount") .objectForKey("pikachu") New .bool(forKey:

    "isUserLoggedIn") .integer(forKey: "pokeballCount") .object(forKey: "pikachu")
  25. Swift 3: Synchronize Old NSUserDefaults.standardUserDefaults().synchronize() New // deprecated !

  26. UserDefaults Stringly typed API // Setter UserDefaults.standard.set(true, forKey: "isUserLoggedIn") //

    Getter UserDefaults.standard.bool(forKey: "isUserLoggedIn")
  27. Fix: Constants UserDefaults.standard.set(true, forKey: "isUserLoggedIn") UserDefaults.standard.bool(forKey: "isUserLoggedIn")

  28. 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)
  29. Fix: Unify struct Constants { struct Keys { // Account

    static let isUserLoggedIn = "isUserLoggedIn" // Onboarding ... } }
  30. // Set UserDefaults.standard .set(true, forKey: Constants.Keys.isUserLoggedIn) // Get UserDefaults.standard .bool(forKey:

    Constants.Keys.isUserLoggedIn)
  31. Fix: Unify with context struct Constants { struct Keys {

    struct Account { static let isUserLoggedIn = "isUserLoggedIn" } } }
  32. // Set UserDefaults.standard .set(true, forKey: Constants.Keys.Account.isUserLoggedIn) // Get UserDefaults.standard .bool(forKey:

    Constants.Keys.Account.isUserLoggedIn)
  33. Fix: Unify Constants.swift struct Account { static let isUserLoggedIn =

    "isUserLoggedIn" }
  34. Fix: Unify with enums Constants.swift enum Account : String {

    case isUserLoggedIn }
  35. struct Constants { struct Keys { enum Account : String

    { case isUserLoggedIn } } }
  36. // Set UserDefaults.standard .set(true, forKey: Constants.Keys.Account.isUserLoggedIn.rawValue) // Get UserDefaults.standard .bool(forKey:

    Constants.Keys.Account.isUserLoggedIn.rawValue)
  37. 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
  38. // Set UserDefaults.standard .set(true, forKey: Constants.Keys.Account.isUserLoggedIn.rawValue) // Get UserDefaults.standard .bool(forKey:

    Constants.Keys.Account.isUserLoggedIn.rawValue)
  39. Hurts our brains

  40. Extend UserDefaults

  41. None
  42. None
  43. None
  44. Extend UserDefaults

  45. tl;dw // Set UserDefaults.standard .set(true, forKey: Constants.Keys.Account.isUserLoggedIn.rawValue) // Get UserDefaults.standard

    .bool(forKey: Constants.Keys.Account.isUserLoggedIn.rawValue)
  46. 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)
  47. Protocols

  48. Protocol: BoolDefaultSettable protocol BoolDefaultSettable { associatedtype BoolKey : RawRepresentable }

  49. None
  50. "For every protocol, there is an equal and corresponding protocol

    extension" — Crusty's Third Law
  51. Protocol: BoolDefaultSettable protocol BoolDefaultSettable { associatedtype BoolKey : RawRepresentable }

    extension BoolDefaultSettable where BoolKey.RawValue == String { }
  52. 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) } }
  53. 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) } }
  54. 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) } }
  55. Protocol: DefaultSettable Family protocol IntegerDefaultSettable { ... } protocol DoubleDefaultSettable

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

    String { case isUserLoggedIn } }
  57. UserDefaults extension extension UserDefaults : BoolDefaultSettable { enum BoolKey :

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

    String { case isUserLoggedIn } } ... Account.set(true, forKey: .isUserLoggedIn) Account.bool(forKey: .isUserLoggedIn)
  59. Problem: Collisions Account.BoolKey.isUserLoggedIn.rawValue // key: "isUserLoggedIn" UserDefaults.BoolKey.isUserLoggedIn.rawValue // key: "isUserLoggedIn"

  60. Protocol: KeyNamespaceable protocol KeyNamespaceable { func namespaced<T: RawRepresentable>(_ key: T)

    -> String }
  61. 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)" } }
  62. 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"
  63. Protocol: BoolDefaultSettable protocol BoolDefaultSettable { associatedtype BoolKey : RawRepresentable }

  64. Protocol: BoolDefaultSettable protocol BoolDefaultSettable : KeyNamespaceable { associatedtype BoolKey :

    RawRepresentable }
  65. 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) } }
  66. 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) } }
  67. Collision free UserDefaults.set(true, forKey: .isUserLoggedIn) // key: "UserDefaults.isUserLoggedIn" Account.set(true, forKey:

    .isUserLoggedIn) // key: "Account.isUserLoggedIn"
  68. What about Uniformity?

  69. Constants.swift extension Account : BoolDefaultSettable { ... } extension Onboarding

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

    : BoolDefaultSettable { ... } struct Constants { ... } ... Account.set(true, forKey: .isUserLoggedIn) Account.bool(forKey: .isUserLoggedIn)
  71. What about Context?

  72. Defaults.swift struct Defaults { struct Account : BoolDefaultSettable { ...

    } struct Onboarding : BoolDefaultSettable { ... } }
  73. 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)
  74. None
  75. So what have we learned?

  76. So what have we learned? • Stringly typed API's are

    bad
  77. So what have we learned? • Stringly typed API's are

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

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

    bad • Plenty of room for improvement • Grouping constants can assist uniformity • Namespacing gives context
  80. 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
  81. 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
  82. SWIFT EYE FOR THE STRINGLY TYPED API Thank You Medium:

    Andyy Hope Twitter: @andyyhope