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

Mixins over Inheritance - FrenchKit '16

AliSoftware
September 23, 2016

Mixins over Inheritance - FrenchKit '16

— My talk @ FrenchKit 2016. Video available here: https://youtu.be/BSn4jlunn4I

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 containing detailed explanations about these topics:

* "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/

AliSoftware

September 23, 2016
Tweet

More Decks by AliSoftware

Other Decks in Technology

Transcript

  1. • 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! ✍ %
  2. THE PROBLEM class BurgerViewController: UIViewController { func setupBurgerMenu() { …

    } override func viewDidLoad() { super.viewDidLoad() setupBurgerMenu() } } class MyViewController1: BurgerViewController { … } class MyViewController2: BurgerViewController { … } class MyViewController3: UITableViewController + BurgerViewController { … } @aligatr
  3. 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() } } @aligatr
  4. 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. @aligatr
  5. 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 } @aligatr
  6. 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) ") }__ }__ @aligatr
  7. 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") } } @aligatr
  8. 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) } @aligatr
  9. REAL-LIFE EXAMPLE class CustomCellOne: UITableViewCell { func fill(withText: String) {

    } } class CustomCellTwo: UITableViewCell { func fill(withIndex: Int) { } } @aligatr
  10. 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) { } } @aligatr
  11. 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) } } @aligatr
  12. 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) } } @aligatr
  13. 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) } } @aligatr
  14. 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) } } @aligatr
  15. 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) } } @aligatr
  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) 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) } } @aligatr
  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) as CustomCellOne cell1.fill(withText: "Hello there!") let cell2: CustomCellTwo = tableView.dequeueReusableCell(indexPath: indexPath) cell2.fill(withIndex: indexPath.row) tableView.register(class: CustomCellOne.self) ✨ @aligatr
  18. 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) } } @aligatr
  19. THANK YOU protocol Talk { func ask(question: String) -> String

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