1

Ideally, I want to create a BaseViewController class that takes in a protocol type (of a delegate) and have a weak variable as the delegate. Something like this:

class BaseViewController<Delegate: AnyObject> {
    weak var delegate: Delegate?

    init(delegate: Delegate) {
        self.delegate = delegate
        super.init(...)
    }
}

And then inherit from a view controller like so:

protocol MyDelegate: AnyObject { 
    func funcA()
    func funcB()
}  

class SomeViewController: BaseViewController<MyDelegate> {
    func doSomething() {
        delegate?.funcA()
    }
}

This doesn't work as the compiler complains:

'BaseViewController' requires that 'MyDelegate' be a class type

How can I work this around to achieve what I need?
Thanks in advance :)

Eilon
  • 2,698
  • 3
  • 16
  • 32
  • Thats because in swift protocols doesn't confirm to them selves, you cant use "MyProtocol" as concrete type confirming to protocol "MyDelegate" example: https://stackoverflow.com/questions/33112559/protocol-doesnt-conform-to-itself – Sandeep Bhandari Jan 15 '21 at 12:10
  • A protocol is not an object. You will need to create a class that conforms to the `MyDelegate` protocol, say `MyDelegateClass`, and use that to specialise your generic. – Paulw11 Jan 15 '21 at 12:10

2 Answers2

1

Thats because in swift protocols doesn't confirm to them selves, you can't use "MyProtocol" as concrete type confirming to protocol "MyDelegate"

What you can rather do is

protocol MyDelegate: AnyObject {
    func funcA()
    func funcB()
}

class BaseViewController<Delegate: MyDelegate> {
    weak var delegate: Delegate?

    init(delegate: Delegate) {
        self.delegate = delegate
        super.init(...)
        //keeping OPs code as is
    }
}

class SomeOtherDelegateClass: MyDelegate {
    func funcA() {
        //some code here
    }

    func funcB() {
        //some code here
    }


}

class SomeViewController: BaseViewController<SomeOtherDelegateClass> {
    func doSomething() {
        self.delegate?.funcA()
    }
}

EDIT 1:

As OP mentioned in comment, he is trying to introduce a generic property in BaseViewController that will simply hold a weak reference to any instance whose class is decided/declared by Child classes of BaseViewController using generics, I am simplifying the above answer a bit

Try this

protocol MyDelegate {
    func funcA()
    func funcB()
}

class BaseViewController<Delegate> where Delegate: AnyObject {
    weak var delegate: Delegate?

    init(delegate: Delegate) {
        self.delegate = delegate
        super.init(...)
        //keeping OPs code as is
    }
}

class SomeOtherDelegateClass: MyDelegate {
    func funcA() {
        //some code here
    }

    func funcB() {
        //some code here
    }
}

class SomeViewController: BaseViewController<SomeOtherDelegateClass> {
    func doSomething() {
        self.delegate?.funcA()
    }
}

protocol MyDelegate2 {
    func funcABCD()
}

class SomeOtherDelegateClass2: MyDelegate2 {
    func funcABCD() {
        //some code here
    }
}


class SomeViewController2: BaseViewController<SomeOtherDelegateClass2> {
    func doSomething() {
        self.delegate?.funcABCD()
    }
}

TBH, I really dont see much of benefit of this design! Probably you need to revisit the code structure and see if you can come up with better code structure :)

Sandeep Bhandari
  • 19,999
  • 5
  • 45
  • 78
  • The thing is that I need the delegate type to be generic and decided by the class inheriting from `BaseViewController`, so I can't have `BaseViewController` have its delegate type bound to `MyDelegate`. – Eilon Jan 15 '21 at 15:20
  • 1
    @eilon: As I already mentioned in answer, swift does not allow protocol to confirm itself, so you cant pass Protocol as a Generic argument `BaseViewController< MyDelegate>` is not allowed. so you need to rethink your code structure, there is very high possibility that you could re design your code to structure it better and avoid such pitfall. – Sandeep Bhandari Jan 15 '21 at 15:33
  • @eilon: Check the edit, see if that helps – Sandeep Bhandari Jan 15 '21 at 15:40
  • @eilon: second edit should somewhat satisfy your requirement :) please check now – Sandeep Bhandari Jan 15 '21 at 15:50
  • 1
    Thank you Sandeep, providing the adopting class to the generic indeed works, although it's not ideal as now my ViewController has access to the entire class interface rather than just the protocol functions. Regardless, I will try thinking of a better implementation. I'm trying to design it that way as this view controller patterns reoccurs many times throughout my application and I want to try cutting down boiler-plate code into a superclass. – Eilon Jan 15 '21 at 16:11
0

You should set your delegate as a constraint for the generic type T in BaseViewController:

protocol MyDelegate: AnyObject {
    func funcA()
    func funcB()
}

class Delegated1: MyDelegate {
    func funcA() { print("A1") }
    func funcB() {}
}

class Delegated2: MyDelegate {
    func funcA() { print("A2") }
    func funcB() {}
}

class BaseViewController<T: MyDelegate>: UIViewController {
    var delegate: T?

    func doSomething() {
        delegate?.funcA()
    }
}

class SomeViewController1: BaseViewController<Delegated1> {}
class SomeViewController2: BaseViewController<Delegated2> {}

class TestClass {
    let viewController1: SomeViewController1 = {
        let viewController = SomeViewController1(nibName: nil, bundle: nil)
        viewController.delegate = .init()
        return viewController
    }()

    let viewController2: SomeViewController2 = {
        let viewController = SomeViewController2(nibName: nil, bundle: nil)
        viewController.delegate = .init()
        return viewController
    }()


    // prints:
    // A1
    // A2
    func myFunc() {
        viewController1.doSomething()
        viewController2.doSomething()

    }
}
Alchi
  • 799
  • 9
  • 19