Mixins over Inheritance — AppDevCon'17

Mixins over Inheritance — AppDevCon'17

My talk about using Mixins in Swift @ AppDevCon 2017

Embark in a journey with some Sci-Fi characters to discover how the Mixins & Traits pattern can help you better design your Swift code, avoid multiple-inheritance problems, and propose a protocol-oriented, Crusty-approved™ alternative to composition!

In this talk you’ll discover what Mixins & Traits are, and how they can help you solve some interesting problems and make your architecture more flexible. Using some concrete examples, we’ll see also how these can be used in everyday challenges.

By digging even further and leveraging things like generics, type inference and POP, we’ll even be able to bring some more magic to our code and make it safer, also allowing us to get rid of String-based API and letting the compiler do all the job for us!

Fb60bbce2fadcc41e950ef8dea3f29e5?s=128

AliSoftware

March 17, 2017
Tweet

Transcript

  1. Mixins over Inheritance Leveraging Mixins and Traits in Swift Olivier

    Halligon March 2017 aligatr
  2. aligatr Bonjour! • iOS Architect & Lead Dev @ Niji

    • Teaching, Sharing, Speaking
 http://alisoftware.github.io 
 @aligatr • OpenSource enthusiast
 http://github.com/AliSoftware
 OHHTTPStubs, SwiftGen, Reusable, CocoaPods, Fastlane !" #✍ %& '(
  3. aligatr Sci-Fi!

  4. aligatr Sci-Fi! Doc Emmett Brown Doctor Who Iron Man Superman

    Time Traveler Flyer Human Alien
  5. aligatr Inheritance? Doc Emmett Brown Doctor Who Iron Man Superman

    Human Alien Character
  6. aligatr Inheritance? Doc Emmett Brown Doctor Who Iron Man Superman

    Time Traveler Flyer Human Alien Character
  7. aligatr Composition! Doc Emmett Brown Doctor Who Iron Man Superman

    Time Machine Human Traits Alien Traits Flying Engine Flying Engine Time Machine Human Traits Alien Traits
  8. aligatr Composition! class DoctorWho { let traits: AlienTraits let tardis:

    TimeMachine let name: String init(name: String, planet: String = "Gallifrey") { self.traits = AlienTraits(planet: planet, hearts: 2) self.tardis = TimeMachine() self.name = name } } class TimeMachine { func travel(to date: Date) { print(")* \(date) +,") } } struct AlienTraits { let planet: String let hearts: Int }
  9. aligatr Composition! let david = DoctorWho() david.tardis.travel(to: Date(timeIntervalSince1970: 1303484520)) //

    )* Apr 22, 2011, 5:02 PM +, print(david.traits.hearts) // 2 let clark = SuperMan() clark.flyingEngine.fly() // flyingEngine?! ! - .
  10. aligatr Mixins? Doc Emmett Brown Doctor Who Iron Man Superman

    Time Traveler Flyer Human Alien Identity / 0 Abilities
  11. aligatr Protocols with Default Implementation protocol Flyer { func fly()

    }__ extension Flyer { func fly() { print("I believe I can flyyyyy ⽄") }__ }__
  12. aligatr Protocols with Default Implementation protocol Flyer { func fly()

    }__ extension Flyer { func fly() { print("I believe I can flyyyyy ⽄") }__ }__ class SuperMan: Flyer { // let Clark sing! } class Bird: Flyer { // we get the default implementation of fly() here } class Plane: Flyer { // we get the default implementation of fly() here } I believe I can flyyyyy ⽄
  13. aligatr Protocols with Default Implementation protocol Flyer { func fly()

    }__ extension Flyer { func fly() { print("I believe I can flyyyyy ⽄") }__ }__ class IronMan: Flyer { let thrusters = Thrusters() // specific implementation when needed func fly() { thrusters.start() } }
  14. aligatr Unlimited Abilities 0 protocol TimeTraveler { func travel(to date:

    Date) } extension TimeTraveler { func travel(to date: Date) { print(")* \(date) +,") } } protocol Flyer { func fly() }__ extension Flyer { func fly() { print("I believe I can flyyyyy ⽄") } } • • •
  15. aligatr One Identity, Multiple Abilities class TimeLord: Alien, TimeTraveler, Flyer

    { init() { super.init(name: "I'm the Doctor", planet: "Gallifrey") } } let doctorWho = TimeLord() doctorWho.travel(to: Date(timeIntervalSince1970: 1303484520)) // )* Apr 22, 2011, 5:02 PM +, doctorWho.fly()
  16. aligatr Adding Abilities extension TimeLord: SpaceTraveler {} extension Superman: SpaceTraveler

    {} protocol SpaceTraveler { func travel(to location: String) } extension SpaceTraveler { func travel(to location: String) { print("Let's go to \(location)!") } } doctorWho.travel(to: "Trenzalore") // prints "Let's go to Trenzalore!"
  17. aligatr Protocol All the Things! struct TimeLord: Alien, TimeTraveler, Flyer,

    SpaceTraveler { let name = "I'm the Doctor" let planet = "Gallifrey" } protocol Named { var name: String { get } } protocol Alien: Named { var planet: String { get } } class Character { let name: String init(name: String) { … } } class Alien: Character { let planet: String init(name: String, planet: String) { … } } « Protocol Oriented Programming is Not a Silver Bullet » — Chris Eidhof
  18. aligatr Back to Boring Reality class SuperSmartViewController: UIViewController, BurgerMenuProvider, Animatable,

    CustomTitleViewProvider { … } extension SuperSmartViewController: Themable { } let vc = SuperSmartViewController() vc.apply(theme: .dark)
  19. aligatr Creating type-safe APIs

  20. aligatr Creating type-safe APIs let cell1 = tableView.dequeueReusableCell(withIdentifier: "CustomCellOne", for:

    indexPath) as! CustomCellOne cell1.fill(withText: "Model One, \(indexPath.row)") class CustomCellOne: UITableViewCell { func fill(withText: String) { } } class CustomCellTwo: UITableViewCell { func fill(withIndex: Int) { } }
  21. aligatr Creating type-safe APIs protocol Reusable: class { static var

    reuseIdentifier: String { get } } extension Reusable { static var reuseIdentifier: String { return String(describing: Self.self) } } let cell1 = tableView.dequeueReusableCell(withIdentifier: CustomCellOne.reuseIdentifier, for: indexPath) as! CustomCellOne cell1.fill(withText: "Model One, \(indexPath.row)") class CustomCellOne: UITableViewCell, Reusable { func fill(withText: String) { } } class CustomCellTwo: UITableViewCell, Reusable { func fill(withIndex: Int) { } }
  22. aligatr Creating type-safe APIs protocol Reusable: class { static var

    reuseIdentifier: String { get } } extension Reusable { static var reuseIdentifier: String { return String(describing: Self.self) } } extension UITableView { func dequeueReusableCell<T>(indexPath: IndexPath) -> T { return self.dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as! T } } class CustomCellOne: UITableViewCell, Reusable { func fill(withText: String) { } } class CustomCellTwo: UITableViewCell, Reusable { func fill(withIndex: Int) { } }
  23. aligatr Creating type-safe APIs protocol Reusable: class { static var

    reuseIdentifier: String { get } } extension Reusable { static var reuseIdentifier: String { return String(describing: Self.self) } } extension UITableView { func dequeueReusableCell<T>(indexPath: IndexPath) -> T where T: UITableViewCell, T: Reusable { return self.dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as! T } } class CustomCellOne: UITableViewCell, Reusable { func fill(withText: String) { } } class CustomCellTwo: UITableViewCell, Reusable { func fill(withIndex: Int) { } }
  24. aligatr Creating type-safe APIs extension UITableView { func dequeueReusableCell<T>(indexPath: IndexPath)

    -> T where T: UITableViewCell, T: Reusable { return self.dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as! T } } let cell1 = tableView.dequeueReusableCell(indexPath: indexPath) cell1.fill(withText: "Model One, \(indexPath.row)") error: generic parameter 'T' could not be inferred
  25. aligatr Creating type-safe APIs extension UITableView { func dequeueReusableCell<T>(indexPath: IndexPath)

    -> T where T: UITableViewCell, T: Reusable { return self.dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as! T } } let cell1: CustomCellOne = tableView.dequeueReusableCell(indexPath: indexPath) cell1.fill(withText: "Model One, \(indexPath.row)")
  26. aligatr Creating type-safe APIs extension UITableView { func dequeueReusableCell<T>(indexPath: IndexPath)

    -> T where T: UITableViewCell, T: Reusable { return self.dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as! T } } let cell1: CustomCellOne = tableView.dequeueReusableCell(indexPath: indexPath) cell1.fill(withText: "Model One, \(indexPath.row)") let cell2 = tableView.dequeueReusableCell(indexPath: indexPath) as CustomCellTwo cell2.fill(withIndex: indexPath.row)
  27. aligatr Creating type-safe APIs class CustomCellOne: UITableViewCell, Reusable { func

    fill(withText: String) { } } class CustomCellTwo: UITableViewCell, Reusable { func fill(withIndex: Int) { } } let cell1: CustomCellOne = tableView.dequeueReusableCell(indexPath: indexPath) cell1.fill(withText: "Model One, \(indexPath.row)") let cell2 = tableView.dequeueReusableCell(indexPath: indexPath) as CustomCellTwo cell2.fill(withIndex: indexPath.row)
  28. aligatr Thank You! protocol Talk { func ask(question: String) ->

    String } extension Talk { func ask(question: String) -> String { return "ya tu sabes" } } http://alisoftware.github.io https://twitter.com/aligatr http://github.com/AliSoftware/Reusable ✍ March 2017 Olivier Halligon I believe I can flyyyyy ⽄