Dynamic dispatch not working? Or not possible?

Hi everyone!

I'm no being able to make a function call to be dynamically dispatched, and wanted to know if someone knows if there is an issue with the runtime, or if my idea of dynamic dispatch is wrong.

I have a SwiftPackage library with the following code:

public protocol ConsolePrinter {
    associatedtype Printable
    dynamic func customPrintable() -> Printable?
    func printToConsole(value: Printable)
}

public extension ConsolePrinter {
    // default implementation to be overriden
    dynamic func customPrintable() -> Printable? {
        return nil
    }

    // printToConsole default implementation with default logic that can be changed by implementing customPrintable
    func printToConsole(value: Printable) {
        if let newValue = customPrintable() {
            print(newValue)
        } else {
            print(value)
        }
    }
}

//Class with default library logic
public class MyClass: ConsolePrinter {
    public typealias Printable = String
    
    //Method that gets calls within the library
    public func printAValue() {
        printToConsole(value: "This is the Swift Package")
    }
}

And an application that depends on that swift package with the following code:

extension MyClass {
    func customPrintable() -> String? {
        return "This is the application"
    }
}

The expected result is that when printAValue() is called, the output would be This is the application, as it is when all the code is in the same module. But the actual output is "This is the Swift Package".

That makes me believe that the implementation is decided at compile-time, and not at runtime dynamically.

Is that the expected behaviour?
Is it possible to actually make that call dynamic at runtime?

Thanks!

There are several things to unpack here.

First, remove the dynamic keyword. It only affects Objective-C interop related to swizzling and KVO.

Second, remove the declaration of printToConsole from the body of the protocol. No type should ever need to write a different implementation, so it should exist solely in the protocol extension.

Third, the witness implementation for each protocol requirement of a conforming type is selected at the point where the type conforms to the protocol, so the behavior you observe is expected.

Fourth, this question is about using Swift, not developing the Swift compiler, so I am moving this thread from Development/Compiler to Using Swift.

It is not possible. Somewhere on this site lays the thread that tries to address that issue, which is to introduce new syntax to make dispatch explicit. This would use an adapter-like approach. You probably could try to create an adapter yourself if that's possible.
Here is the link: An inheritance problem

I believe you can still swizzle customPrintable yourself since it's dynamic, though I wouldn't really recommend that. You could just subclass MyClass (or straight up write adapter) to override it.

One more option to consider: don’t conform MyClass to ConsolePrinter in the library, but conform in the extension in the main app. Then witness table will capture customPrintable() from the extension.

Thanks for the clarifications, and sorry about the tag, didn't see that one at first sight.

Thanks everyone, with all the help I managed to implement a decent solution, will still keep looking for a cleaner one.

It's hard to simplify it to the relevant parts, but the solution is something like:

Swift Package

public protocol Printable { }

extension Printable {
    func print() {
        Swift.print(self)
    }
}

public protocol CustomConsolePrinter {
    func customPrintable() -> Printable?
}

public protocol ConsolePrinter {
    associatedtype T: Printable
}

public extension ConsolePrinter {

    func printToConsole(value: T) {
        if let self = self as? CustomConsolePrinter, let printable = self.customPrintable() {
            printable.print()
        } else {
            value.print()
        }
    }
}

//Class with default library logic
extension String: Printable { }

public class MyClass: ConsolePrinter {
    public typealias T = String
    
    //Method that gets calls within the library
    public func printAValue() {
        printToConsole(value: "This is the Swift Package")
    }
}

Application:

extension MyClass: CustomConsolePrinter {
    
    public func customPrintable() -> Printable? {
        return "This is the application"
    }
}

Regards

Terms of Service

Privacy Policy

Cookie Policy