Mixins over Inheritance

Fb60bbce2fadcc41e950ef8dea3f29e5?s=47 AliSoftware
September 15, 2016

Mixins over Inheritance

— My talk @ NSSpain 2016

Embark in a journey with some Sci-Fi characters to discover how the Mixins & Traits pattern can help you better design your classes, 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 avoid the problems otherwise brought by inheritance and be an interesting alternative to composition, by leveraging the Protocol-Oriented features of Swift.

We'll start by demonstrating the limitations of inheritance (and even composition) in some concrete examples, then present the concept of Mixins & Traits.

With the help of some fun examples and a bunch of Sci-Fi characters, we'll then see how this Mixins & Traits pattern can solve those problems, make your architecture more flexible, protocol-oriented… and allowing to factorize your code in a new way.

We'll finish the talk with a very concrete use case of Mixins that will allow you to get rid of String-based APIs like UITableViewCells,, UICollectionViews and UIStoryboard APIs, and make your code type-safe thanks to this Swift magic! ✨🎩

---

Read more on my blog posts:

* "Mixins over Inheritance": http://alisoftware.github.io/swift/protocol/2015/11/08/mixins-over-inheritance/
* "Using Generics to improve TableView cells": http://alisoftware.github.io/swift/generics/2016/01/06/generic-tableviewcells/

Fb60bbce2fadcc41e950ef8dea3f29e5?s=128

AliSoftware

September 15, 2016
Tweet

Transcript

  1. MIXINS OVER INHERITANCE Leveraging Mixins and Traits in Swift Olivier

    Halligon September 2016
  2. • iOS Architect
 Niji • Teaching, Sharing, Speaking
 http://alisoftware.github.io
 https://twitter.com/aligatr

    • OpenSource enthusiast
 http://github.com/AliSoftware
 OHHTTPStubs, SwiftGen, Dip, Reusable, CocoaPods… HELLO WORLD! ✍ %
  3. THE PROBLEM class BurgerViewController: UIViewController { func setupBurgerMenu() { …

    } override func viewDidLoad() { super.viewDidLoad() setupBurgerMenu() } } class MyViewController1: BurgerViewController { … } class MyViewController2: BurgerViewController { … } class MyViewController3: UITableViewController + BurgerViewController { … }
  4. COMPOSITION! class BurgerMenuManager { func setupBurgerMenu() { … } func

    onBurgerMenuTapped() { … } var burgerMenuIsOpen = false { didSet { … } } } class MyViewController1: UIViewController { let menuManager = BurgerMenuManager() override func viewDidLoad() { super.viewDidLoad() menuManager.setupBurgerMenu() } } class MyViewController3: UITableViewController { let menuManager = BurgerMenuManager() override func viewDidLoad() { super.viewDidLoad() menuManager.setupBurgerMenu() } } class MyViewController2: UIViewController { let menuManager = BurgerMenuManager() override func viewDidLoad() { super.viewDidLoad() menuManager.setupBurgerMenu() } }
  5. MIXINS & TRAITS

  6. MIXINS & TRAITS Doc Emmett Brown Doctor Who Iron Man

    Superman Time Traveler Flyer Human Alien Identities Abilities Inheritance let you describe what an object is. Traits let you describe what an object can do.
  7. PROTOCOLS
 WITH DEFAULT IMPLEMENTATION protocol TimeTraveler { func travel(to: NSDate)

    }__ extension TimeTraveler { func travel(to: NSDate) { print(" \(date) ") }__ }__ class TimeLord: TimeTraveler { // we don't need to implement travel(to:) there } let doctorWho = TimeLord() doctorWho.travel(to: NSDate(timeIntervalSince1970: 1303484520)) // prints " Apr 22, 2011, 5:02 PM " let docBrown = DocEmmettBrown() docBrown.travel(to: NSDate(timeIntervalSince1970: 499161600)) // prints " Oct 26, 1985, 9:00 AM " class DocEmmettBrown: TimeTraveler { // we don't need to implement travel(to:) there }
  8. ONE IDENTITY, MULTIPLE ABILITIES protocol Flyer { func fly() }

    extension Flyer { func fly() { print("I believe I can flyyyyy ⽄") } } class Character { let name: String init(name: String) { … } } class Human: Character { let country: String init(name: String, country: String = "") { … } } class Alien: Character { let planet: String init(name: String, planet: String) { … } } Identities Abilities protocol TimeTraveler { func travel(to: NSDate) }__ extension TimeTraveler { func travel(to: NSDate) { print(" \(date) ") }__ }__
  9. ONE IDENTITY, MULTIPLE ABILITIES protocol Flyer { func fly() }

    protocol TimeTraveler { func travel(to: NSDate) } class Character { let name: String } class Human: Character { let country: String } class Alien: Character { let planet: String } class TimeLord: Alien, TimeTraveler { init() { super.init(name: "I'm the Doctor", planet: "Gallifrey") } } class DocEmmettBrown: Human, TimeTraveler { init() { super.init(name: "Emmett Brown", country: "USA") } } class Superman: Alien, Flyer { init() { super.init(name: "Clark Kent", planet: "Krypton") } } class IronMan: Human, Flyer { init() { super.init(name: "Tony Stark", country: "USA") } }
  10. ADDING ABILITIES extension TimeLord: SpaceTraveler {} extension Superman: SpaceTraveler {}

    protocol SpaceTraveler { func travel(to: String) } extension SpaceTraveler { func travel(to: String) { print("Let's go to \(location)!") } } doctorWho.travel(to: "Trenzalore") // prints "Let's go to Trenzalore!" class TimeLord: Alien, TimeTraveler class DocEmmettBrown: Human, TimeTraveler class Superman: Alien, Flyer class IronMan: Human, Flyer class Character class Human: Character class Alien: Character protocol Flyer { func fly() } protocol TimeTraveler { func travel(to: NSDate) }
  11. REAL-LIFE EXAMPLE class CustomCellOne: UITableViewCell { func fill(withText: String) {

    } } class CustomCellTwo: UITableViewCell { func fill(withIndex: Int) { } }
  12. REAL-LIFE EXAMPLE protocol Reusable: class { static var reuseIdentifier: String

    { get } } extension Reusable { static var reuseIdentifier: String { return String(reflecting: Self.self) } } let type: Reusable.Type = CustomCellOne.self tableView.dequeueReusableCell(withIdentifier: type.reuseIdentifier, for: indexPath) class CustomCellOne: UITableViewCell, Reusable { func fill(withText: String) { } } class CustomCellTwo: UITableViewCell, Reusable { func fill(withIndex: Int) { } }
  13. REAL-LIFE EXAMPLE extension UITableView { func dequeueReusableCell(indexPath: IndexPath, type: Reusable.Type)

    -> UITableViewCell { return self.dequeueReusableCell(withIdentifier: type.reuseIdentifier, for: indexPath) } } let cell1 = tableView.dequeueReusableCell(indexPath: indexPath, type: CustomCellOne.self) class CustomCellOne: UITableViewCell, Reusable { func fill(withText: String) { } } class CustomCellTwo: UITableViewCell, Reusable { func fill(withIndex: Int) { } } UITableViewCell protocol Reusable: class { static var reuseIdentifier: String { get } } extension Reusable { static var reuseIdentifier: String { return String(reflecting: Self.self) } }
  14. REAL-LIFE EXAMPLE extension UITableView { func dequeueReusableCell<T: Reusable>(indexPath: IndexPath, type:

    T.Type) -> UITableViewCell { return self.dequeueReusableCell(withIdentifier: type.reuseIdentifier, for: indexPath) } } class CustomCellOne: UITableViewCell, Reusable { func fill(withText: String) { } } class CustomCellTwo: UITableViewCell, Reusable { func fill(withIndex: Int) { } } let cell1 = tableView.dequeueReusableCell(indexPath: indexPath, type: CustomCellOne.self) protocol Reusable: class { static var reuseIdentifier: String { get } } extension Reusable { static var reuseIdentifier: String { return String(reflecting: Self.self) } }
  15. REAL-LIFE EXAMPLE extension UITableView { func dequeueReusableCell<T: Reusable>(indexPath: IndexPath, type:

    T.Type) -> T where T: UITableViewCell { return self.dequeueReusableCell(withIdentifier: type.reuseIdentifier, for: indexPath) as! T } } class CustomCellOne: UITableViewCell, Reusable { func fill(withText: String) { } } class CustomCellTwo: UITableViewCell, Reusable { func fill(withIndex: Int) { } } let cell1 = tableView.dequeueReusableCell(indexPath: indexPath, type: CustomCellOne.self) cell1.fill(withText: "Hello there!") protocol Reusable: class { static var reuseIdentifier: String { get } } extension Reusable { static var reuseIdentifier: String { return String(reflecting: Self.self) } }
  16. REAL-LIFE EXAMPLE extension UITableView { func dequeueReusableCell<T: Reusable>(indexPath: IndexPath, type:

    T.Type = T.self) -> T where T: UITableViewCell { return self.dequeueReusableCell(withIdentifier: type.reuseIdentifier, for: indexPath) as! T } } class CustomCellOne: UITableViewCell, Reusable { func fill(withText: String) { } } class CustomCellTwo: UITableViewCell, Reusable { func fill(withIndex: Int) { } } let cell1 = tableView.dequeueReusableCell(indexPath: indexPath, type: CustomCellOne.self) cell1.fill(withText: "Hello there!") protocol Reusable: class { static var reuseIdentifier: String { get } } extension Reusable { static var reuseIdentifier: String { return String(reflecting: Self.self) } }
  17. REAL-LIFE EXAMPLE extension UITableView { func dequeueReusableCell<T: Reusable>(indexPath: IndexPath, type:

    T.Type = T.self) -> T where T: UITableViewCell { return self.dequeueReusableCell(withIdentifier: type.reuseIdentifier, for: indexPath) as! T } } class CustomCellOne: UITableViewCell, Reusable { func fill(withText: String) { } } class CustomCellTwo: UITableViewCell, Reusable { func fill(withIndex: Int) { } } let cell1 = tableView.dequeueReusableCell(indexPath: indexPath) cell1.fill(withText: "Hello there!") protocol Reusable: class { static var reuseIdentifier: String { get } } extension Reusable { static var reuseIdentifier: String { return String(reflecting: Self.self) } }
  18. REAL-LIFE EXAMPLE extension UITableView { func dequeueReusableCell<T: Reusable>(indexPath: IndexPath, type:

    T.Type = T.self) -> T where T: UITableViewCell { return self.dequeueReusableCell(withIdentifier: type.reuseIdentifier, for: indexPath) as! T } } class CustomCellOne: UITableViewCell, Reusable { func fill(withText: String) { } } class CustomCellTwo: UITableViewCell, Reusable { func fill(withIndex: Int) { } } let cell1 = tableView.dequeueReusableCell(indexPath: indexPath) as CustomCellOne cell1.fill(withText: "Hello there!") protocol Reusable: class { static var reuseIdentifier: String { get } } extension Reusable { static var reuseIdentifier: String { return String(reflecting: Self.self) } }
  19. REAL-LIFE EXAMPLE extension UITableView { func dequeueReusableCell<T: Reusable>(indexPath: IndexPath, type:

    T.Type = T.self) -> T where T: UITableViewCell { return self.dequeueReusableCell(withIdentifier: type.reuseIdentifier, for: indexPath) as! T } } class CustomCellOne: UITableViewCell, Reusable { func fill(withText: String) { } } class CustomCellTwo: UITableViewCell, Reusable { func fill(withIndex: Int) { } } let cell1 = tableView.dequeueReusableCell(indexPath: indexPath) as CustomCellOne cell1.fill(withText: "Hello there!") let cell2: CustomCellTwo = tableView.dequeueReusableCell(indexPath: indexPath) cell2.fill(withIndex: indexPath.row) tableView.register(class: CustomCellOne.self) ✨
  20. REAL-LIFE EXAMPLE class CustomCellOne: UITableViewCell, Reusable { func fill(withText: String)

    { … } } class CustomXIBCell: UICollectionViewCell, NibReusable { func fill(indexPath: NSIndexPath) { … } } class InfoViewController: UIViewController, StoryboardBased { … } let infoVC = InfoViewController.instantiate() infoVC.setInfo(self.info) class MyCustomWidget: UIView, NibOwnerLoadable { @IBInspectable var rectColor: UIColor? required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) MyCustomWidget.loadFromNib(owner: self) } }
  21. THANK YOU protocol Talk { func ask(question: String) -> String

    } extension Talk { func ask(question: String) -> String { return "ya tu sabes" } } • http://github.com/AliSoftware/Reusable • http://alisoftware.github.io • https://twitter.com/aligatr ✍ September 2016 Olivier Halligon