Search code examples
iosswiftuiviewcontrollerswift-protocolsswift-extensions

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


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


Solution

  • 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 =)