$30 off During Our Annual Pro Sale. View Details »

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!

AliSoftware

March 17, 2017
Tweet

More Decks by AliSoftware

Other Decks in Programming

Transcript

  1. Mixins over Inheritance
    Leveraging Mixins and Traits in Swift
    Olivier Halligon
    March 2017
    aligatr

    View Slide

  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
    !"
    #✍
    %&
    '(

    View Slide

  3. aligatr
    Sci-Fi!

    View Slide

  4. aligatr
    Sci-Fi!
    Doc Emmett Brown
    Doctor Who
    Iron Man
    Superman
    Time Traveler
    Flyer
    Human
    Alien

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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
    }

    View Slide

  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?!
    !
    -
    .

    View Slide

  10. aligatr
    Mixins?
    Doc Emmett Brown
    Doctor Who
    Iron Man
    Superman
    Time Traveler
    Flyer
    Human
    Alien
    Identity / 0 Abilities

    View Slide

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

    View Slide

  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 ⽄

    View Slide

  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()
    }
    }

    View Slide

  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 ⽄")
    }
    }
    • • •

    View Slide

  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()

    View Slide

  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!"

    View Slide

  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

    View Slide

  18. aligatr
    Back to Boring Reality
    class SuperSmartViewController: UIViewController,
    BurgerMenuProvider, Animatable, CustomTitleViewProvider
    {

    }
    extension SuperSmartViewController: Themable { }
    let vc = SuperSmartViewController()
    vc.apply(theme: .dark)

    View Slide

  19. aligatr
    Creating type-safe APIs

    View Slide

  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) { }
    }

    View Slide

  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) { }
    }

    View Slide

  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(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) { }
    }

    View Slide

  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(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) { }
    }

    View Slide

  24. aligatr
    Creating type-safe APIs
    extension UITableView {
    func dequeueReusableCell(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

    View Slide

  25. aligatr
    Creating type-safe APIs
    extension UITableView {
    func dequeueReusableCell(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)")

    View Slide

  26. aligatr
    Creating type-safe APIs
    extension UITableView {
    func dequeueReusableCell(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)

    View Slide

  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)

    View Slide

  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 ⽄

    View Slide