Can Swift Method Defined on Extensions on Protocol

2019-02-11 20:43发布

问题:

Is it possible to call methods defined in a protocol extension in Swift from Objective-C?

For example:

protocol Product {
    var price:Int { get }
    var priceString:String { get }
}

extension Product {
    var priceString:String {
        get {
            return "$\(price)"
        }
    }
}

class IceCream : Product {
    var price:Int {
        get {
            return 2
        }
    }
}

The price string of an instance of IceCream is '$2' and can be accessed in Swift, however the method is not visible in Objective-C. The compiler throws the error 'No visible @interface for 'IceCream' declares the selector ...'.

In my configuration, if the method is defined directly in the Swift object's implementation, everything works as expected. i.e.:

protocol Product {
    var price:Int { get }
    var priceString:String { get }
}

class IceCream : Product {
    var price:Int {
        get {
            return 2
        }
    }
    var priceString:String {
        get {
            return "$\(price)"
        }
    }
}

回答1:

I am nearly certain the answer to this is "no", although I haven't found official Apple documentation that spells it out.

Here is a message from the swift-evolution mailing list discussing the proposal to use dynamic dispatch for all method calls, which would provide calling semantics more like Objective-C:

Again, the sole exception to this is protocol extensions. Unlike any other construct in the language, protocol extension methods are dispatched statically in a situation where a virtual dispatch would cause different results. No compiler error prevents this mismatch. (https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/001707.html)

Protocol extensions are a Swift-only language feature, and as such are not visible to objc_msgSend().



回答2:

If it is ok to remove the priceString from the protocol, and only have it in your extension, you can call the protocol extension by casting IceCream to a Product in a helper extension.

@objc protocol Product {
    var price:Int { get }
}

extension Product {
    var priceString:String {
        return "$\(price)"
    }
}

// This is the trick
// Helper extension to be able to call protocol extension from obj-c
extension IceCream : Product {
    var priceString:String {
        return (self as Product).priceString
    }
}

@objc class IceCream: NSObject {
    var price: Int {
        return 2
    }
}


回答3:

Protocol extension does not work with @objc protocol, however you can extend the class in swift as a workaround.

@objc protocol Product {
   var price: NSNumber? { get }
   var priceString:String { get }
}

...
// IceCream defined in Objective-C that does not extend Product
// but has @property(nonatomic, strong, nullable) (NSNumber*)price
// to make it confirm to Product
...
extension IceCream: Product {
   var priceString:String {
      get {
        return "$\(price ?? "")"
      }
   }
}

This code is not clean at all, but it works.