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

Mixins over Inheritance

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/

AliSoftware

September 15, 2016
Tweet

More Decks by AliSoftware

Other Decks in Technology

Transcript

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

    View Slide

  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!


    %

    View Slide

  3. THE PROBLEM

    class BurgerViewController: UIViewController {
    func setupBurgerMenu() { … }
    override func viewDidLoad() {
    super.viewDidLoad()
    setupBurgerMenu()
    }
    }
    class MyViewController1: BurgerViewController {

    }
    class MyViewController2: BurgerViewController {

    }
    class MyViewController3: UITableViewController + BurgerViewController {

    }

    View Slide

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

    View Slide

  5. MIXINS & TRAITS

    View Slide

  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.

    View Slide

  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
    }

    View Slide

  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) ")
    }__
    }__

    View Slide

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

    View Slide

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

    View Slide

  11. REAL-LIFE EXAMPLE
    class CustomCellOne: UITableViewCell {
    func fill(withText: String) { }
    }
    class CustomCellTwo: UITableViewCell {
    func fill(withIndex: Int) { }
    }

    View Slide

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

    View Slide

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

    View Slide

  14. REAL-LIFE EXAMPLE
    extension UITableView {
    func dequeueReusableCell(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)
    }
    }

    View Slide

  15. REAL-LIFE EXAMPLE
    extension UITableView {
    func dequeueReusableCell(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)
    }
    }

    View Slide

  16. REAL-LIFE EXAMPLE
    extension UITableView {
    func dequeueReusableCell(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)
    }
    }

    View Slide

  17. REAL-LIFE EXAMPLE
    extension UITableView {
    func dequeueReusableCell(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)
    }
    }

    View Slide

  18. REAL-LIFE EXAMPLE
    extension UITableView {
    func dequeueReusableCell(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)
    }
    }

    View Slide

  19. REAL-LIFE EXAMPLE
    extension UITableView {
    func dequeueReusableCell(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)


    View Slide

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

    View Slide

  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

    View Slide