Swift protocol extension method dispatch with supe

2020-02-11 03:15发布

问题:

I found an interesting behaviour which seems like a bug...

Based on the behaviour described the following articles:

https://medium.com/ios-os-x-development/swift-protocol-extension-method-dispatch-6a6bf270ba94

http://nomothetis.svbtle.com/the-ghost-of-swift-bugs-future

The output is not what I expect, when I add SomeSuperclass, rather than directly adopting the protocol.

protocol TheProtocol {
    func method1()
}

extension TheProtocol {
    func method1() {
        print("Called method1 from protocol extension")
    }
    func method2NotInProtocol() {
        print("Called method2NotInProtocol from protocol extension")
    }
}

// This is the difference - adding a superclass
class SomeSuperclass: TheProtocol {
}

// It works as expected when it simply adopts TheProtocol, but not when it inherits from a class that adopts the protocol
class MyClass: SomeSuperclass {
    func method1() {
        print("Called method1 from MyClass implementation")
    }
    func method2NotInProtocol() {
        print("Called method2NotInProtocol from MyClass implementation")
    }
}

let foo: TheProtocol = MyClass()
foo.method1()  // expect "Called method1 from MyClass implementation", got "Called method1 from protocol extension"
foo.method2NotInProtocol()  // Called method2NotInProtocol from protocol extension

Do you know if this is a bug, or by design? A colleague suggested perhaps mixing inheritance and protocol extensions might not work as expected. I was intending to use the protocol extension to provide a default implementation... if I can't do it, then I will sadly have to mark it @objc and go back to an optional protocol.

回答1:

From the post The Ghost of Swift Bugs Future, here are the rules for dispatch for protocol extensions that are mentioned at the end of the post.

  1. IF the inferred type of a variable is the protocol:
  2. AND the method is defined in the original protocol THEN the runtime type’s implementation is called, irrespective of whether there is a default implementation in the extension.
  3. AND the method is not defined in the original protocol, THEN the default implementation is called.
  4. ELSE IF the inferred type of the variable is the type THEN the type’s implementation is called.

So in your condition, you're saying that method1() is defined in the protocol and it has been implemented in the subclass. But your superclass is adopting the protocol but it is not implementing the method1() and subclass just inherits from the Superclass and doesn't adopt to the protocols directly. That's why I believe that is the reason when you call foo.method1(), it doesn't invoke the the subclass implementation as stated by point 1 & 2.

But when you do,

class SomeSuperclass: TheProtocol {
func method1(){
 print("super class implementation of method1()")}
}

class MyClass : SomeSuperclass {

override func method1() {
    print("Called method1 from MyClass implementation")
}

override func method2NotInProtocol() {
    print("Called method2NotInProtocol from MyClass implementation")
}
}

and then when you call,

 let foo: TheProtocol = MyClass()
foo.method1()  // Called method1 from MyClass implementation
foo.method2NotInProtocol() 

So what could be the workaround for this bug (which seems to be a bug) is that, you need to implement the protocol method in the superclass and then you need to override the protocol method in the sub class. HTH



回答2:

Please check the code below:

import UIKit

protocol TheProtocol {
    func method1()
    func method2NotInProtocol()
}

extension NSObject {

    func method1() {
        print("Called method1 from protocol extension")
    }
    func method2NotInProtocol() {
        print("Called method2NotInProtocol from protocol extension")
    }
}

// This is the difference - adding a superclass
class SomeSuperclass :NSObject, TheProtocol {

    override func method1() {
        print("Called method1 from SomeSuperclass implementation")
    }

}

// It works as expected when it simply adopts TheProtocol, but not when it inherits from a class that adopts the protocol
class MyClass : SomeSuperclass {

    override func method1() {
        print("Called method1 from MyClass implementation")
    }

    override func method2NotInProtocol() {
        print("Called method2NotInProtocol from MyClass implementation")
    }
}

    let foo: TheProtocol = MyClass()
    foo.method1()  // expect "Called method1 from MyClass implementation", got "Called method1 from protocol extension"
    foo.method2NotInProtocol()  // Called method2NotInProtocol from protocol extension

Instead of writing extension to TheProtocol, write extension to a abstract class(NSObject in above code). This works as expected.



回答3:

An option that hasn't been considered is to split up your protocol into several smaller ones, such that the superclass does not need to conform to protocols containing methods it does not intend to implement. The subclass can then subscribe to these other protocols separately.