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

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

Posted by: admin January 4, 2018 Leave a comment

Questions:

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!!

Answers:

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

Questions:
Answers:

Well, thus far I’ve found no truly satisfactory way of doing it, but I decided to post what I ended up doing for this particular problem.
In a nutshell, the solution goes like this (using the original example code):

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

extension UIViewController //------->simple extension on UIViewController directly
{
    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)

        //------->only run when self conforms to MyProtocol
        if let protocolConformingSelf = self as? MyProtocol { 
            //invoke APIs from  
            protocolConformingSelf.protocolFunction() // MyProtocol APIs
            let viewLoaded = protocolConformingSelf.isViewLoaded // UIViewController APIs
        }

    }

}

Drawbacks:

  • Not “the Swift way” of doing things
  • The swizzling method will be invoked and take effect on ALL UIViewControllers, even though we validate for only those that conform to the MyProtocol protocol to run the sensitive lines of code.

I very much hope it helps anyone else out there facing a similar situation =)