Possible to attach logic to method dynamically?

592 views Asked by At

I'd like to dynamically attach a closure to another method from the init of a class. For example, for UIViewController I'd like to add an extension so I can inject code in the viewDidLoad event. I tried something like below, but it doesn't work:

class BaseViewController: UIViewController, MyProtocol {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        // Setup and bind
        configure()
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Call MyProtocol.myDidLoad but not explicitly!
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        // Call viewWillAppear but not explicitly!
    }

}

protocol MyProtocol { }

extension MyProtocol where Self: UIViewController {

    func configure() {
        // Something like below isn't possible???
        self.viewDidLoad += myDidLoad
        self.viewWillAppear += myWillAppear
    }

    func myDidLoad() {
        // Something
    }

    func myWillAppear() {
        // Something
    }
}

Is achieving this possible, without explicitly calling the protocol functions from the UIViewController? I don't want to have to go through all my classes and call the protocol functions for each function. That would be tedious, redundant code, and can easily be overlooked. Is there a more elegant way?

UPDATE:

Below is the only way I can think of given the limitations, but it is using inheritance over composition and is still requiring me to explicitly call the protocol extension functions, which is not what I want to do:

class FirstViewController: BaseViewController {

}

class BaseViewController: UIViewController, MyProtocol, MyProtocol2, MyProtocol3 {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        // Configure protocols
        configure(self as MyProtocol)
        configure(self as MyProtocol2)
        configure(self as MyProtocol3)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Load protocols
        viewDidLoad(self as MyProtocol)
        viewDidLoad(self as MyProtocol2)
        viewDidLoad(self as MyProtocol3)
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        // Prerender protocols
        viewWillAppear(self as MyProtocol)
        viewWillAppear(self as MyProtocol2)
        viewWillAppear(self as MyProtocol3)
    }

}

protocol MyProtocol { }

extension MyProtocol where Self: UIViewController {

    func configure(delegate: MyProtocol) {
        print("MyProtocol.configure")
    }

    func viewDidLoad(delegate: MyProtocol) {
        print("MyProtocol.viewDidLoad")
    }

    func viewWillAppear(delegate: MyProtocol) {
        print("MyProtocol.viewWillAppear")
    }
}

protocol MyProtocol2 { }

extension MyProtocol2 where Self: UIViewController {

    func configure(delegate: MyProtocol2) {
        print("MyProtocol2.configure")
    }

    func viewDidLoad(delegate: MyProtocol2) {
        print("MyProtocol2.viewDidLoad")
    }

    func viewWillAppear(delegate: MyProtocol2) {
        print("MyProtocol2.viewWillAppear")
    }
}

protocol MyProtocol3 { }

extension MyProtocol3 where Self: UIViewController {

    func configure(delegate: MyProtocol3) {
        print("MyProtocol3.configure")
    }

    func viewDidLoad(delegate: MyProtocol3) {
        print("MyProtocol3.viewDidLoad")
    }

    func viewWillAppear(delegate: MyProtocol3) {
        print("MyProtocol3.viewWillAppear")
    }
}

//Output:
//MyProtocol.configure
//MyProtocol2.configure
//MyProtocol3.configure
//MyProtocol.viewDidLoad
//MyProtocol2.viewDidLoad
//MyProtocol3.viewDidLoad
//MyProtocol.viewWillAppear
//MyProtocol2.viewWillAppear
//MyProtocol3.viewWillAppear

This defeats the whole purpose of Protocol Oriented Programming. Is there an elegant approach that Swift can handle when working in the confines of iOS development, or POP doesn't work in this case?

1

There are 1 answers

2
Paulw11 On BEST ANSWER

What you want to do isn't possible as both Swift and Objective-C are single-inheritance languages.

An extension can only add additional methods, it cannot override methods. Only a subclass can override methods in an existing class. Consider the case where multiple extensions overrode the same method - which one should be called? All of them?, if so in what order? See The Diamond Problem

The simplest approach is to create a UIViewController subclass that provides the functionality you want. Although it is possible for a programmer to create a class that conforms to the protocol but doesn't subclass the correct superclass, considering the class and the protocol are entered on the same line, it would be a pretty silly error.