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

Stupid Enum Tricks in Swift and Kotlin - We Are Developers, Vienna, Austria, May 2018

Stupid Enum Tricks in Swift and Kotlin - We Are Developers, Vienna, Austria, May 2018

Enums in Swift and enum classes in Kotlin are extremely powerful ways to deal with a known set of possible values. Learn how to use these tools to help you cleaner, safer code, and how to push the boundaries of what's possible with enums.

Ellen Shapiro

May 16, 2018
Tweet

More Decks by Ellen Shapiro

Other Decks in Technology

Transcript

  1. STUPID ENUM TRICKS
    IN SWIFT AND KOTLIN
    WE ARE DEVELOPERS | VIENNA, AUSTRIA | MAY 2018
    BAKKENBAECK.COM | JUSTHUM.COM | DESIGNATEDNERD.COM
    BY ELLEN SHAPIRO | @DESIGNATEDNERD

    View full-size slide

  2. PREFACE:
    A NOTE ON PRONUNCIATION

    View full-size slide

  3. "EE-NUMB" VS. "EE-NOOM"

    View full-size slide

  4. "EE-NUMB" VS. "EE-NOOM"

    (I PRONOUNCE IT THIS WAY)

    View full-size slide

  5. "EE-NUMB" VS. "EE-NOOM"
    (THIS WAY IS ALSO FINE)

    View full-size slide

  6. WHAT IS AN ENUM?

    View full-size slide

  7. A LIST OF VALUES THAT ARE
    POSSIBLE

    View full-size slide

  8. POSSIBLE
    IT COULD BE ANY ONE OF THESE THINGS IN THE LIST

    View full-size slide

  9. SWIFT
    enum ColorName: String {
    case red
    case orange
    case yellow
    case green
    case blue
    case violet
    }

    View full-size slide

  10. IT COULD ALSO BE SOMETHING
    TOTALLY DIFFERENT

    View full-size slide

  11. SWIFT
    func colorForName(_ name: String) -> UIColor? {
    switch name {
    case ColorName.red.rawValue:
    return UIColor.red
    case ColorName.orange.rawValue:
    return UIColor.orange
    // same for other ColorName cases
    default:
    return nil
    }

    View full-size slide

  12. EXHAUSTIVE ENUMS

    View full-size slide

  13. EXAUSTIVE
    BUT THESE ARE THE ONLY THINGS WHICH ARE POSSIBLE

    View full-size slide

  14. SWIFT
    enum TrafficLightColor {
    case red
    case yellow
    case green
    }

    View full-size slide

  15. LIMIT YOUR
    POSSIBILITIES

    View full-size slide

  16. LIMIT YOUR
    RUNTIME ERRORS

    View full-size slide

  17. SWIFT STANDARD LIBRARY
    public enum ComparisonResult : Int {
    case orderedAscending
    case orderedSame
    case orderedDescending
    }

    View full-size slide

  18. KOTLIN STANDARD LIBRARY
    public enum ClassKind {
    CLASS,
    INTERFACE,
    ENUM_CLASS,
    ENUM_ENTRY,
    ANNOTATION_CLASS,
    OBJECT;
    }

    View full-size slide

  19. HOW DO I CREATE
    MY OWN ENUM?

    View full-size slide

  20. SWIFT
    enum TrafficLightColor {
    case red
    case yellow
    case green
    }

    View full-size slide

  21. KOTLIN
    enum class

    View full-size slide

  22. KOTLIN
    enum class TrafficLightColor {
    Red,
    Yellow,
    Green;
    }

    View full-size slide

  23. HOW DO I USE ENUMS?

    View full-size slide

  24. switch / when

    View full-size slide

  25. SWIFT
    var currentColor: TrafficLightColor = .red
    switch currentColor {
    case .red:
    print("STOP RIGHT THERE")
    case .yellow:
    print("Whoa, slow down there, buddy.")
    case .green:
    print("Go, go, go!")
    }

    View full-size slide

  26. SWIFT
    var currentColor: TrafficLightColor = .red
    switch currentColor {
    case .red:
    print("STOP RIGHT THERE")
    case .green:
    print("Go, go, go!")
    // Error: Unhandled case!
    }

    View full-size slide

  27. SWIFT
    var currentColor: TrafficLightColor = .red
    switch currentColor {
    case .red:
    print("STOP RIGHT THERE")
    case .yellow,
    .green:
    print("Go, go, go!")
    }

    View full-size slide

  28. KOTLIN
    var currentColor = TrafficLightColor.Red
    when (currentColor) {
    TrafficLightColor.Red -> println("STOP RIGHT THERE")
    TrafficLightColor.Yellow -> println("Whoa, slow down there, buddy.")
    TrafficLightColor.Green -> println("Go, go, go!")
    }

    View full-size slide

  29. WHAT DO I GET
    FOR FREE WITH ENUMS?

    View full-size slide

  30. KOTLIN: A LOT!

    View full-size slide

  31. KOTLIN
    Instance Gets You Example
    instance.name String value of
    case's written name
    Days.THURSDAY.name
    is "THURSDAY",
    Days.Thursday.name
    is "Thursday"
    instance.ordinal Index of case in
    list of cases

    View full-size slide

  32. KOTLIN
    Type Gets You
    Type.valueOf(string:
    String)
    Enum value of string, or
    null
    Type.values() Generated list of all
    values in the enum class

    View full-size slide

  33. SWIFT: IT DEPENDS

    View full-size slide

  34. BY DEFAULT: NOT MUCH

    View full-size slide

  35. RawRepresentable

    View full-size slide

  36. SWIFT
    enum SettingsSection {
    case profile
    case contact
    case legalese
    case logout
    }

    View full-size slide

  37. SWIFT
    enum SettingsSection: Int {
    case profile
    case contact
    case legalese
    case logout
    }

    View full-size slide

  38. SWIFT
    enum SettingsSection: Int {
    case profile // 0
    case contact // 1
    case legalese // 2
    case logout // 3
    }

    View full-size slide

  39. SWIFT
    enum SettingsSection: Int {
    case profile // 0
    case legalese // 1
    case contact // 2
    case logout // 3
    }

    View full-size slide

  40. SWIFT
    guard let section = SettingsSection(rawValue: 10) else {
    assertionFailure("Section 10 does not exist!")
    }

    View full-size slide

  41. SWIFT
    enum JSONKey: String {
    case user_name
    case email_address
    case latitude
    case longitude
    }

    View full-size slide

  42. SWIFT
    enum JSONKey: String {
    case user_name
    case email_address
    case latitude = "lat"
    case longitude = "long"
    }

    View full-size slide

  43. SWIFT Codable
    class User: Codable {
    let userName: String
    let email: String
    let latitude: Double
    let longitude: Double
    enum CodingKeys: String, CodingKey {
    case userName = "user_name"
    case email = "email_address"
    case latitude = "lat"
    case longitude = "long"
    }
    }

    View full-size slide

  44. SWIFT 4.2: CaseIterable

    View full-size slide

  45. SWIFT
    enum SettingsSection: Int, CaseIterable {
    case profile
    case legalese
    case contact
    case logout
    }

    View full-size slide

  46. SWIFT
    enum SettingsSection: Int, CaseIterable {
    case profile
    case legalese
    case contact
    case logout
    }
    // Automatically generated:
    static var allCases: [SettingsSection]

    View full-size slide

  47. SWIFT
    func numberOfSectionsInTableView(_ tableView: UITableView) -> Int {
    return SettingsSection.allCases.count
    }
    func tableView(_ tableView: UITableView,
    titleForHeaderInSection section: Int) -> String {
    let section = SettingsSection.allCases[section]
    return section.localizedTitle
    }

    View full-size slide

  48. WHAT CAN I
    ADD TO ENUMS?

    View full-size slide

  49. COMPUTED PROPERTIES

    View full-size slide

  50. SWIFT
    enum LandingScreenButton {
    case signIn
    case signUp
    case viewTerms
    }

    View full-size slide

  51. SWIFT
    enum LandingScreenButton {
    case signIn
    case signUp
    case viewTerms
    var localizedTitle: String {
    switch self {
    case .signIn:
    return NSLocalizedString("Sign In",
    "Sign in button title")
    case .signUp:
    return NSLocalizedString("Sign Up", "Sign up button title")
    case .viewTerms:
    return NSLocalizedString("View Terms & Conditions", "Title for button to view legalese")
    }
    }

    View full-size slide

  52. SWIFT
    public enum LandingScreenButton {
    case signIn
    case signUp
    case viewTerms
    public var localizedTitle: String {
    switch self {
    case .signIn:
    return NSLocalizedString("Sign In", "Sign in button title")
    case .signUp:
    return NSLocalizedString("Sign Up", "Sign up button title")
    case .viewTerms:
    return NSLocalizedString("View Terms & Conditions", "Title for button to view legalese")
    }
    }

    View full-size slide

  53. SWIFT / XCUI

    View full-size slide

  54. KOTLIN /
    ESPRESSO

    View full-size slide

  55. ASSET HANDLING [IOS]

    View full-size slide

  56. ASSET CATALOGS

    View full-size slide

  57. SWIFT / IOS: BEFORE
    guard let image =
    UIImage(named: "cinnamon_rolls") else {
    return
    }

    View full-size slide

  58. SWIFT / IOS: BEFORE
    guard let image =
    UIImage(named: "cinnamon_rolls") else {
    return
    }

    View full-size slide

  59. SWIFT / IOS: BEFORE
    guard let image =
    UIImage(named: "cinamon_rolls") else {
    return
    }

    View full-size slide

  60. SWIFT / IOS: BEFORE
    guard let image =
    UIImage(named: "cinamon_rolls") else {
    return
    }

    View full-size slide

  61. SWIFT / IOS: AFTER
    public enum Asset: String {
    case cinnamon_rolls
    case innocent
    case no
    case snack
    case window
    var image: UIImage {
    return UIImage(named: self.rawValue)!
    }
    }

    View full-size slide

  62. SWIFT / IOS: TESTS!
    func testAllAssetsAreThere() {
    XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue))
    XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue))
    XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue))
    XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue))
    XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue))
    }

    View full-size slide

  63. SWIFT / IOS: TESTS!
    func testAllAssetsAreThere() {
    XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue))
    XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue))
    XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue))
    XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue))
    XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue))
    } !

    View full-size slide

  64. SWIFT / IOS: TESTS!
    func testAllAssetsAreThere() {
    for asset in Asset.allCases {
    XCTassertNotNil(UIImage(named: asset.rawValue),
    "Image for \(asset.rawValue) was nil!")
    }
    }

    View full-size slide

  65. SWIFT / IOS: TESTS!
    func testAllAssetsAreThere() {
    for asset in Asset.allCases {
    XCTassertNotNil(UIImage(named: asset.rawValue),
    "Image for \(asset.rawValue) was nil!")
    }
    } !!!!!!!

    View full-size slide

  66. USE CODE GENERATION

    View full-size slide

  67. USE CODE GENERATION
    (OR THIS CAN GET SUPER-TEDIOUS ON BIG PROJECTS)

    View full-size slide

  68. SWIFT CODE GENERATION
    > https://github.com/SwiftGen/SwiftGen
    > https://github.com/krzysztofzablocki/Sourcery
    > https://github.com/stencilproject/Stencil

    View full-size slide

  69. BENDING ENUMS
    TO YOUR WILL

    View full-size slide

  70. COMBINING ENUMS
    +
    PROTOCOLS / INTERFACES

    View full-size slide

  71. SWIFT: BEFORE
    @IBAction private func signIn() {
    guard self.validates() else { return }
    performSegue(withIdentifier: "toCatsLoggedIn", sender: nil)
    }

    View full-size slide

  72. SWIFT: THE ENUM
    enum MainStoryboardSegue: String {
    case toSignIn
    case toSignUp
    case toTerms
    case toCats
    case toCatsLoggedIn
    }

    View full-size slide

  73. SWIFT: THE PROTOCOL
    protocol SeguePerforming {
    var rawValue: String { get }
    }

    View full-size slide

  74. SIDE NOTE:
    LEARN TO CHEAT

    View full-size slide

  75. SWIFT: THE PROTOCOL WITH A GENERIC EXTENSION FUNCTION
    protocol SeguePerforming {
    var rawValue: String { get }
    }
    extension UIViewController {
    func perform(segue: T, sender: Any? = nil) {
    performSegue(withIdentifier: segue.rawValue, sender: sender)
    }
    }

    View full-size slide

  76. SWIFT: THE ENUM, REVISED
    enum MainStoryboardSegue: String, SeguePerforming {
    case toSignIn
    case toSignUp
    case toTerms
    case toCats
    case toCatsLoggedIn
    }

    View full-size slide

  77. SWIFT: AFTER
    @IBAction private func signIn() {
    guard self.validates() else { return }
    self.perform(MainStoryboardSegue.toCats)
    }

    View full-size slide

  78. ASSOCIATED OBJECTS

    View full-size slide

  79. SWIFT
    enum DownloadState {
    case notStarted,
    case downloading(let progress: Float)
    case success(let data: Data)
    case error(let error: Error?)
    }

    View full-size slide

  80. KOTLIN
    enum class CreditCard(val prefix: String) {
    Visa("4"),
    Mastercard("5"),
    Amex("3");
    }

    View full-size slide

  81. KOTLIN
    sealed class

    View full-size slide

  82. KOTLIN
    sealed class DownloadStateWithInfo {
    }

    View full-size slide

  83. KOTLIN
    sealed class DownloadStateWithInfo {
    class NotStarted: DownloadStateWithInfo()
    }

    View full-size slide

  84. KOTLIN
    sealed class DownloadStateWithInfo {
    class NotStarted: DownloadStateWithInfo()
    class Downloading(val progress: Float): DownloadStateWithInfo()
    }

    View full-size slide

  85. KOTLIN
    sealed class DownloadStateWithInfo {
    class NotStarted: DownloadStateWithInfo()
    class Downloading(val progress: Float): DownloadStateWithInfo()
    class Succeeded(val data: ByteArray): DownloadStateWithInfo()
    }

    View full-size slide

  86. KOTLIN
    sealed class DownloadStateWithInfo {
    class NotStarted: DownloadStateWithInfo()
    class Downloading(val progress: Float): DownloadStateWithInfo()
    class Succeeded(val data: ByteArray): DownloadStateWithInfo()
    class Failed(val error: Error): DownloadStateWithInfo()
    }

    View full-size slide

  87. BLACK MAGIC WITH

    GENERICS

    View full-size slide

  88. KOTLIN
    enum class KeyboardType {
    QWERTY,
    ABCDEF,
    QWERTZ,
    AZERTY;
    }

    View full-size slide

  89. KOTLIN
    inline fun > T.next(): T {
    val currentIndex = this.ordinal
    val nextIndex = currentIndex + 1
    val allValues = enumValues()
    return if (nextIndex >= allValues.size) {
    allValues[0]
    } else {
    allValues[nextIndex]
    }
    }

    View full-size slide

  90. KOTLIN
    inline fun > T.next(): T {
    // val currentIndex = this.ordinal
    // val nextIndex = currentIndex + 1
    // val allValues = enumValues()
    // return if (nextIndex >= allValues.size) {
    // allValues[0]
    // } else {
    // allValues[nextIndex]
    // }
    }

    View full-size slide

  91. KOTLIN
    inline fun > T.next(): T {
    // val currentIndex = this.ordinal
    // val nextIndex = currentIndex + 1
    val allValues = enumValues()
    // return if (nextIndex >= allValues.size) {
    // allValues[0]
    // } else {
    // allValues[nextIndex]
    // }
    }

    View full-size slide

  92. KOTLIN
    inline fun > T.next(): T {
    // val currentIndex = this.ordinal
    // val nextIndex = currentIndex + 1
    // val allValues = enumValues()
    return if (nextIndex >= allValues.size) {
    allValues[0]
    } else {
    allValues[nextIndex]
    }
    }

    View full-size slide

  93. KOTLIN
    KeyboardType.QWERTY.next() // returns KeyboardType.ABCDEF
    KeyboardType.ABCDEF.next() // returns KeyboardType.QWERTZ
    KeyboardType.QWERTZ.next() // returns KeyboardType.AZERTY
    KeyboardType.AZERTY.next() // returns KeyboardType.QWERTY

    View full-size slide

  94. KOTLIN
    KeyboardType.QWERTY.next()
    KeyboardType.ABCDEF.next()
    KeyboardType.QWERTZ.next()
    KeyboardType.AZERTY.next()

    View full-size slide

  95. THE LIMITS
    OF ENUM AWESOMESAUCE

    View full-size slide

  96. ASSOCIATED OBJECTS = AWESOME
    LISTABILITY = AWESOME
    LISTABILITY + ASSOCIATED OBJECTS =

    View full-size slide

  97. KOTLIN
    sealed class DownloadStateWithInfo {
    class NotStarted: DownloadStateWithInfo()
    class Downloading(val progress: Float): DownloadStateWithInfo()
    class Succeeded(val data: ByteArray): DownloadStateWithInfo()
    class Failed(val error: Error): DownloadStateWithInfo()
    }

    View full-size slide

  98. KOTLIN
    DownloadStateWithInfo.NotStarted().next()

    View full-size slide

  99. KOTLIN
    DownloadStateWithInfo.NotStarted().next() !"!

    View full-size slide

  100. A LITTLE EASY TO GET
    CARRIED AWAY

    View full-size slide

  101. SWIFT: GOOD
    enum LandingScreenButton {
    // Stuff we saw before
    var localizedTitle: String {
    switch self {
    case .signIn:
    return NSLocalizedString("Sign In",
    "Sign in button title")
    case .signUp:
    return NSLocalizedString("Sign Up",
    "Sign up button title")
    case .viewTerms:
    return NSLocalizedString("View Terms & Conditions",
    "Title for button to view legalese")
    }
    }

    View full-size slide

  102. SWIFT: OVER THE TOP
    enum LandingScreenButton {
    // Stuff we saw before
    func handleClick(in viewController: UIViewController) {
    switch self {
    case .signIn:
    viewController.navigationController?
    .pushViewController(SignInViewController(), animated: true)
    case .signUp:
    viewController.navigationController?
    .pushViewController(RegistrationViewController(), animated: true)
    case .viewTerms:
    viewController.present(LegaleseViewController(), animated: true)
    }
    }
    }

    View full-size slide

  103. IS THIS FUNCTIONALITY
    INHERENTLY RELATED
    TO THE THING I'M REPRESENTING?

    View full-size slide

  104. SEPERATION OF CONCERNS
    STILL APPLIES!

    View full-size slide

  105. OBLIGATORY SUMMARY SLIDE

    View full-size slide

  106. OBLIGATORY SUMMARY SLIDE
    > Enums are a great way to represent distinct state

    View full-size slide

  107. OBLIGATORY SUMMARY SLIDE
    > Enums are a great way to represent distinct state
    > Limit your cases, limit your bugs

    View full-size slide

  108. OBLIGATORY SUMMARY SLIDE
    > Enums are a great way to represent distinct state
    > Limit your cases, limit your bugs
    > Value determined by the current case -> computed var

    View full-size slide

  109. OBLIGATORY SUMMARY SLIDE
    > Enums are a great way to represent distinct state
    > Limit your cases, limit your bugs
    > Value determined by the current case -> computed var
    > Generated enums help reduce stringly-typed code

    View full-size slide

  110. OBLIGATORY SUMMARY SLIDE
    > Enums are a great way to represent distinct state
    > Limit your cases, limit your bugs
    > Value determined by the current case -> computed var
    > Generated enums help reduce stringly-typed code
    > Don't forget about separation of concerns

    View full-size slide

  111. CODE EXAMPLES IN SWIFT AND KOTLIN!
    https://github.com/designatednerd/
    StupidEnumTricks

    View full-size slide

  112. LINKS!
    > The Swift book's section on Enums:
    https://developer.apple.com/library/
    content/documentation/Swift/Conceptual/
    Swift_Programming_Language/
    Enumerations.html
    > Kotlin Enum Class documentation:
    https://kotlinlang.org/docs/reference/
    enum-classes.html

    View full-size slide