为什么Swift没有用更具体的类型调用我的重载方法?

时间:2017-05-05 09:31:07

标签: json swift overloading

我使用Decodable从JSON解码一个简单的结构。这符合Decodable协议:

extension BackendServerID: Decodable {

    static func decode(_ json: Any) throws -> BackendServerID {
        return try BackendServerID(
            id: json => "id",
            name: json => "name"
        )
    }
}

我希望能够使用decode致电String,所以我添加了一个扩展程序:

extension Decodable {

    static func decode(_ string: String) throws -> Self {
        let jsonData = string.data(using: .utf8)!
        let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
        return try decode(jsonObject)
    }
}

然后我想像这样解码对象:

XCTAssertNoThrow(try BackendServerID.decode("{\"id\": \"foo\", \"name\": \"bar\"}"))

但是,这不会按预期工作,因为某种方式会调用decode(Any)方法而不是decode(String)。我究竟做错了什么? (当我通过将自定义方法重命名为decodeString来澄清呼叫时,它可以正常工作。)

5 个答案:

答案 0 :(得分:5)

我同意这种行为令人惊讶,你可能想要file a bug over it

快速查看CSRanking.cpp的来源,这是类型检查器实现的一部分,用于处理"排名"对于重载决策的不同声明 - 我们可以看到在执行:

/// \brief Determine whether the first declaration is as "specialized" as
/// the second declaration.
///
/// "Specialized" is essentially a form of subtyping, defined below.
static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
                                  ValueDecl *decl1, ValueDecl *decl2) {

类型检查器认为具体类型中的过载更多"专门"而不是协议扩展中的重载(source):

  // Members of protocol extensions have special overloading rules.
  ProtocolDecl *inProtocolExtension1 = outerDC1
                                         ->getAsProtocolExtensionContext();
  ProtocolDecl *inProtocolExtension2 = outerDC2
                                         ->getAsProtocolExtensionContext();
  if (inProtocolExtension1 && inProtocolExtension2) {
    // Both members are in protocol extensions.
    // Determine whether the 'Self' type from the first protocol extension
    // satisfies all of the requirements of the second protocol extension.
    bool better1 = isProtocolExtensionAsSpecializedAs(tc, outerDC1, outerDC2);
    bool better2 = isProtocolExtensionAsSpecializedAs(tc, outerDC2, outerDC1);
    if (better1 != better2) {
      return better1;
    }
  } else if (inProtocolExtension1 || inProtocolExtension2) {
    // One member is in a protocol extension, the other is in a concrete type.
    // Prefer the member in the concrete type.
    return inProtocolExtension2;
  }

当执行重载分辨率时,类型检查器将跟踪"得分"对于每个潜在的过载,选择一个最高的。当给定的过载被认为更多"专门的"而且,它的分数会增加,因此意味着它会受到青睐。还有其他因素会影响超载的得分,但isDeclAsSpecializedAs似乎是这种特殊情况下的决定因素。

因此,如果我们考虑一个最小的例子,类似于@Sulthan gives

protocol Decodable {
    static func decode(_ json: Any) throws -> Self
}

struct BackendServerID {}

extension Decodable {
    static func decode(_ string: String) throws -> Self {
        return try decode(string as Any)
    }
}

extension BackendServerID : Decodable {
    static func decode(_ json: Any) throws -> BackendServerID {
        return BackendServerID()
    }
}

let str = try BackendServerID.decode("foo")

调用BackendServerID.decode("foo")时,具体类型BackendServerID中的重载首选到协议扩展中的重载(BackendServerID重载的事实是在具体类型的扩展中,这里没有区别)。在这种情况下,无论在函数签名本身是否更加专业化,这都是一样的。 位置更重要。

(虽然函数签名确实在涉及泛型时很重要 - 请参阅下面的切线)

值得注意的是,在这种情况下,我们可以通过在调用时强制转换方法来强制Swift使用我们想要的重载:

let str = try (BackendServerID.decode as (String) throws -> BackendServerID)("foo")

现在将调用协议扩展中的重载。

如果重载都在BackendServerID中定义:

extension BackendServerID : Decodable {
    static func decode(_ json: Any) throws -> BackendServerID {
        return BackendServerID()
    }

    static func decode(_ string: String) throws -> BackendServerID {
        return try decode(string as Any)
    }
}

let str = try BackendServerID.decode("foo")

类型检查器实现中的上述条件不会被触发,因为它们都不在协议扩展中 - 因此当涉及到重载分辨率时,更多的"专用"过载将完全基于签名。因此,将为String参数调用String重载。

(关于通用重载的轻微切线......)

值得注意的是,类型检查器中存在(很多)其他规则,以确定是否将一个重载视为更多"专用"比另一个。其中一个更喜欢非泛型重载到泛型重载(source):

  // A non-generic declaration is more specialized than a generic declaration.
  if (auto func1 = dyn_cast<AbstractFunctionDecl>(decl1)) {
    auto func2 = cast<AbstractFunctionDecl>(decl2);
    if (func1->isGeneric() != func2->isGeneric())
      return func2->isGeneric();
  }

此条件比协议扩展条件更高实现 - 因此,如果您要更改协议中的decode(_:)要求,使其使用通用占位符:

protocol Decodable {
    static func decode<T>(_ json: T) throws -> Self
}

struct BackendServerID {}

extension Decodable {
    static func decode(_ string: String) throws -> Self {
        return try decode(string as Any)
    }
}

extension BackendServerID : Decodable {
    static func decode<T>(_ json: T) throws -> BackendServerID {
        return BackendServerID()
    }
}

let str = try BackendServerID.decode("foo")

现在将调用String重载而不是通用重载,尽管它在协议扩展中。

实际上,正如您所看到的,有很多复杂因素的很多决定了要调用的重载。正如其他人已经说过的,在这种情况下,真正最好的解决方案是通过为String重载提供参数标签来明确消除重载的歧义:

extension Decodable {
    static func decode(jsonString: String) throws -> Self {
        // ...
    }
}

// ...

let str = try BackendServerID.decode(jsonString: "{\"id\": \"foo\", \"name\": \"bar\"}")

这不仅可以解决重载决策问题,还可以使API更加清晰。仅使用decode("someString"),就不清楚字符串应该采用什么格式(XML?CSV?)。现在很清楚它需要一个JSON字符串。

答案 1 :(得分:1)

让我们考虑最小的例子:

protocol Decodable {
    static func decode(_ json: Any) throws -> Self
}

struct BackendServerID {
}

extension Decodable {
    static func decode(_ string: String) throws -> Self {
        return try decode(string)
    }
}

extension BackendServerID : Decodable {
    static func decode(_ json: Any) throws -> BackendServerID {
        return BackendServerID()
    }
}

decodeBackendServerId的实现取代了Decodable.decode的默认实现(参数是协变的,类似于覆盖的情况)。只有当两个函数都在同一级别声明时,您的用例才有效,例如:

extension BackendServerID : Decodable {
    static func decode(_ json: Any) throws -> BackendServerID {
        return BackendServerID()
    }

    static func decode(_ string: String) throws -> Self {
        return try decode(string as Any)
    }
}

另请注意防止递归所必需的as Any

为防止混淆,您应该以不同的方式命名接受stringAny的功能,例如decode(string:)decode(json:)

答案 2 :(得分:0)

我认为你应该覆盖decode(Any)或者你可以做那样的事情

extension Decodable {

    static func decode(String string: String) throws -> Self {
        let jsonData = string.data(using: .utf8)!
        let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
        return try decode(jsonObject)
    }
}

此处您定义新方法decode(String string: String),因此不会调用decode(Any)方法。

答案 3 :(得分:0)

Swift应该调用最具体的实现,你可以在游乐场中尝试确认;所以你的期望是正确的。

在您的情况下,我怀疑问题在于访问控制级别。

在此Decodable库中,方法func decode(_ json: Any)被声明为public,因此它在您的测试代码中可用。

另一方面,您自己的方法func decode(_ string: String)似乎不是public,默认情况下是internal,并且在测试代码中无法访问。

要解决此问题,请使用@testable导入应用程序框架(使所有内部符号可用),或声明方法public

答案 4 :(得分:0)

看来你正在将你的函数添加到两个不同的东西&#34;,第一个函数被添加到<Route path="/create" component={Iframe} />并返回BackendServerID,第二个函数被添加到{ {1}}协议并返回BackendServerID。以下内容适用于游乐场:

Decodable

会打印

Decodable

我认为这应该是你所期望的。

然而,你的两个静态函数没有完全相同的签名,即使他们已经认为&#34;重载&#34;相同的功能。稍微解释了@Sulthan我试过了

protocol Decodable {
    static func decode(_ json: Any)
}

extension Decodable {
    static func decode(_ json: String) {
        print("Hi, I am String-Json: ", json)
    }

    static func decode(_ json: Int8) {
        print("Hi, I am Int8-Json: ", json)
    }

    static func decode(_ json: Any) {
        print("Hi, I am Any-Json: ", "(I do not know how to print whatever you gave me)")
    }
}

extension Decodable {
    static func decode(_ json: Int) {
        print("Hi, I am Int-Json: ", json)
    }
}

class JSONParser : Decodable {
}

let five : Int8 = 5
JSONParser.decode(Int(five))
JSONParser.decode(five)
JSONParser.decode("five")
JSONParser.decode(5.0)

我得到了

Hi, I am Int-Json:  5
Hi, I am Int8-Json:  5
Hi, I am String-Json:  five
Hi, I am Any-Json:  (I do not know how to print whatever you gave me)

(正如你现在可能想到的那样)。 protocol Decodable { static func decode(_ json: Any) throws -> Self } struct BackendServerID { } extension Decodable { static func decode(_ string: String) throws -> BackendServerID { print("decoding as String: ", string) return BackendServerID() } } extension BackendServerID : Decodable { static func decode(_ json: Any) throws -> BackendServerID { print("decoding as Any: ", "(no idea what I can do with this)") return BackendServerID() } } try BackendServerID.decode("hello") 功能是&#34;阴影&#34;和静态函数无法通过其协议类型访问,但如果我将其重命名为

decoding as Any:  (no idea what I can do with this)

我能做到

Decodable

并获得预期结果

extension Decodable {
    static func decodeS(_ string: String) throws -> BackendServerID {
        print("decoding as String: ", string)
        return BackendServerID()
    }
}

另一方面,你可以做到

try BackendServerID.decode("hello")
try BackendServerID.decodeS("hello")

并获取

decoding as Any:  (no idea what I can do with this)
decoding as String:  hello

具有重载功能(但它在第二个extension BackendServerID : Decodable { static func decode(_ json: Any) throws -> BackendServerID { print("decoding as Any: ", "(no idea what I can do with this)") return BackendServerID() } } extension BackendServerID { static func decode(_ string: String) throws -> BackendServerID { print("decoding as String: ", string) return BackendServerID() } } try BackendServerID.decode("hello") try BackendServerID.decode(5) 上不会接受另一个decoding as String: hello decoding as Any: (no idea what I can do with this) )。但是,具体类型和协议的: Decodable不会混合,这很可能是好东西(TM)。

顺便说一下:我试图哄它,但是

extension

只会编译它返回

extensions

因为extension Decodable { static func decode(_ string: String) throws -> Self { print("decoding as String: ", string) return try BackendServerID.decode(string as Any) as! Self } } try BackendServerID.decode("hello") try BackendServerID.decode(5) 上的decoding as Any: (no idea what I can do with this) decoding as Any: (no idea what I can do with this) 版本仍然存在。但无论如何,很高兴看到Swift可以灵活地切换参数类型。

然而,您可能会对

感到失望
String

印刷

Decodable

所以你的整个调度都是以静态方式发生的。如果您获得let five : Any = "five" try BackendServerID.decode(five) ,则似乎无法避免decoding as Any: (no idea what I can do with this) 确定动态类型。