Swift extension of a class ONLY when it conforms to a specific protocol

Robertibiris picture Robertibiris · Nov 17, 2016 · Viewed 7.8k times · Source

Hi there =) I was just faced with a design problem where I need to (essentially) do the following:

I want to inject a bit of code on viewWillAppear: of any UIViewController subclass that conforms to a protocol MyProtocol. Explained in code:

protocol MyProtocol
{
    func protocolFunction() {
        //do cool stuff...
    }
}

extension UIViewController where Self: MyProtocol //<-----compilation error
{
    public override class func initialize()
    {
        //swizzling stuff switching viewWillAppear(_: Bool) with xxx_viewWillAppear(animated: Bool)
    }

    //  MARK: - Swizzling

    func xxx_viewWillAppear(animated: Bool)
    {
        self.xxx_viewWillAppear(animated)

        //invoke APIs from  
        self.protocolFunction() // MyProtocol APIs
        let viewLoaded = self.isViewLoaded // UIViewController APIs

    }
}

The main issue here is that I need to 2 two things in the UIVIewController extension:

  1. Invoke both MyProtocol and UIViewController API's
  2. Override UIViewController method initialize() in order to be able to swizzle viewWillAppear:

These 2 capabilities seem incompatible (as of Swift 3) because:

  1. We can't extend classes with conditions (i.e extension UIViewController where Self: MyProtocol)
  2. if we instead extend the protocol, we CAN add conditions extension MyProtocol where Self: UIViewController but we CAN'T override methods from a class in a protocol extension, meaning we can't public override class func initialize() which is needed for swizzling.

So I was wondering if there's somebody out there who can offer a Swifty solution to this problem I'm facing? =)

Thanks in advance!!

Answer

Klemen picture Klemen · Apr 14, 2017

You were near the solution. Just need to make it other way around. Extend protocol only if its part of UIViewController.

protocol MyProtocol
{
  func protocolFunction() {
    //do cool stuff...
  }
}

extension MyProtocol where Self: UIViewController {
  public override class func initialize()
  {
    //swizzling stuff switching viewWillAppear(_: Bool) with xxx_viewWillAppear(animated: Bool)
  }

  //  MARK: - Swizzling

  func xxx_viewWillAppear(animated: Bool)
  {
    self.xxx_viewWillAppear(animated)

    //invoke APIs from  
    self.protocolFunction() // MyProtocol APIs
    let viewLoaded = self.isViewLoaded // UIViewController APIs
  }
}