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.

C4861b1dfdf3bbb21faec4a1acdf183d?s=128

Ellen Shapiro

May 16, 2018
Tweet

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
  2. PREFACE: A NOTE ON PRONUNCIATION

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

  4. "EE-NUMB" VS. "EE-NOOM" ⬆ (I PRONOUNCE IT THIS WAY)

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

  6. GIF

  7. GIF

  8. WHAT IS AN ENUM?

  9. A LIST OF VALUES THAT ARE POSSIBLE

  10. OPTION SET

  11. OPTION SET

  12. POSSIBLE IT COULD BE ANY ONE OF THESE THINGS IN

    THE LIST
  13. SWIFT enum ColorName: String { case red case orange case

    yellow case green case blue case violet }
  14. IT COULD ALSO BE SOMETHING TOTALLY DIFFERENT

  15. 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 }
  16. EXHAUSTIVE ENUMS

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

  18. SWIFT enum TrafficLightColor { case red case yellow case green

    }
  19. LIMIT YOUR POSSIBILITIES

  20. LIMIT YOUR RUNTIME ERRORS

  21. SWIFT STANDARD LIBRARY public enum ComparisonResult : Int { case

    orderedAscending case orderedSame case orderedDescending }
  22. KOTLIN STANDARD LIBRARY public enum ClassKind { CLASS, INTERFACE, ENUM_CLASS,

    ENUM_ENTRY, ANNOTATION_CLASS, OBJECT; }
  23. HOW DO I CREATE MY OWN ENUM?

  24. SWIFT enum TrafficLightColor { case red case yellow case green

    }
  25. KOTLIN enum class

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

  27. HOW DO I USE ENUMS?

  28. switch / when

  29. 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!") }
  30. SWIFT var currentColor: TrafficLightColor = .red switch currentColor { case

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

    .red: print("STOP RIGHT THERE") case .yellow, .green: print("Go, go, go!") }
  32. 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!") }
  33. WHAT DO I GET FOR FREE WITH ENUMS?

  34. KOTLIN: A LOT!

  35. 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
  36. 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
  37. SWIFT: IT DEPENDS

  38. BY DEFAULT: NOT MUCH

  39. RawRepresentable

  40. SWIFT enum SettingsSection { case profile case contact case legalese

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

    legalese case logout }
  42. SWIFT enum SettingsSection: Int { case profile // 0 case

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

    legalese // 1 case contact // 2 case logout // 3 }
  44. SWIFT guard let section = SettingsSection(rawValue: 10) else { assertionFailure("Section

    10 does not exist!") }
  45. SWIFT enum JSONKey: String { case user_name case email_address case

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

    latitude = "lat" case longitude = "long" }
  47. 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" } }
  48. SWIFT 4.2: CaseIterable

  49. SWIFT

  50. SWIFT enum SettingsSection: Int, CaseIterable { case profile case legalese

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

    case contact case logout } // Automatically generated: static var allCases: [SettingsSection]
  52. 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 }
  53. WHAT CAN I ADD TO ENUMS?

  54. COMPUTED PROPERTIES

  55. SWIFT enum LandingScreenButton { case signIn case signUp case viewTerms

    }
  56. 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") } }
  57. 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") } }
  58. UI TESTING

  59. SWIFT / XCUI

  60. KOTLIN / ESPRESSO

  61. ASSET HANDLING [IOS]

  62. ASSET CATALOGS

  63. SWIFT / IOS: BEFORE guard let image = UIImage(named: "cinnamon_rolls")

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

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

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

    else { return }
  67. 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)! } }
  68. 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)) }
  69. 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)) } !
  70. SWIFT / IOS: TESTS! func testAllAssetsAreThere() { for asset in

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

    Asset.allCases { XCTassertNotNil(UIImage(named: asset.rawValue), "Image for \(asset.rawValue) was nil!") } } !!!!!!!
  72. USE CODE GENERATION

  73. USE CODE GENERATION (OR THIS CAN GET SUPER-TEDIOUS ON BIG

    PROJECTS)
  74. SWIFT CODE GENERATION > https://github.com/SwiftGen/SwiftGen > https://github.com/krzysztofzablocki/Sourcery > https://github.com/stencilproject/Stencil

  75. BENDING ENUMS TO YOUR WILL

  76. COMBINING ENUMS + PROTOCOLS / INTERFACES

  77. SWIFT: BEFORE @IBAction private func signIn() { guard self.validates() else

    { return } performSegue(withIdentifier: "toCatsLoggedIn", sender: nil) }
  78. SWIFT: THE ENUM enum MainStoryboardSegue: String { case toSignIn case

    toSignUp case toTerms case toCats case toCatsLoggedIn }
  79. SWIFT: THE PROTOCOL protocol SeguePerforming { var rawValue: String {

    get } }
  80. SIDE NOTE: LEARN TO CHEAT

  81. SWIFT: THE PROTOCOL WITH A GENERIC EXTENSION FUNCTION protocol SeguePerforming

    { var rawValue: String { get } } extension UIViewController { func perform<T: SeguePerforming>(segue: T, sender: Any? = nil) { performSegue(withIdentifier: segue.rawValue, sender: sender) } }
  82. SWIFT: THE ENUM, REVISED enum MainStoryboardSegue: String, SeguePerforming { case

    toSignIn case toSignUp case toTerms case toCats case toCatsLoggedIn }
  83. SWIFT: AFTER @IBAction private func signIn() { guard self.validates() else

    { return } self.perform(MainStoryboardSegue.toCats) }
  84. ASSOCIATED OBJECTS

  85. SWIFT enum DownloadState { case notStarted, case downloading(let progress: Float)

    case success(let data: Data) case error(let error: Error?) }
  86. KOTLIN enum class CreditCard(val prefix: String) { Visa("4"), Mastercard("5"), Amex("3");

    }
  87. KOTLIN sealed class

  88. KOTLIN sealed class DownloadStateWithInfo { }

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

  90. KOTLIN sealed class DownloadStateWithInfo { class NotStarted: DownloadStateWithInfo() class Downloading(val

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

    progress: Float): DownloadStateWithInfo() class Succeeded(val data: ByteArray): DownloadStateWithInfo() }
  92. 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() }
  93. BLACK MAGIC WITH ✨ GENERICS

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

  95. KOTLIN inline fun <reified T: Enum<T>> T.next(): T { val

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

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

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

    val currentIndex = this.ordinal // val nextIndex = currentIndex + 1 // val allValues = enumValues<T>() return if (nextIndex >= allValues.size) { allValues[0] } else { allValues[nextIndex] } }
  99. 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
  100. KOTLIN KeyboardType.QWERTY.next() KeyboardType.ABCDEF.next() KeyboardType.QWERTZ.next() KeyboardType.AZERTY.next()

  101. THE LIMITS OF ENUM AWESOMESAUCE

  102. ASSOCIATED OBJECTS = AWESOME LISTABILITY = AWESOME LISTABILITY + ASSOCIATED

    OBJECTS =
  103. 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() }
  104. KOTLIN DownloadStateWithInfo.NotStarted().next()

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

  106. A LITTLE EASY TO GET CARRIED AWAY

  107. 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") } }
  108. 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) } } }
  109. IS THIS FUNCTIONALITY INHERENTLY RELATED TO THE THING I'M REPRESENTING?

  110. SEPERATION OF CONCERNS STILL APPLIES!

  111. OBLIGATORY SUMMARY SLIDE

  112. OBLIGATORY SUMMARY SLIDE > Enums are a great way to

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

    represent distinct state > Limit your cases, limit your bugs
  114. 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
  115. 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
  116. 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
  117. CODE EXAMPLES IN SWIFT AND KOTLIN! https://github.com/designatednerd/ StupidEnumTricks

  118. 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