zou*_*oul 6 json overloading swift
我使用Decodable从JSON解码一个简单的结构.这符合Decodable协议:
extension BackendServerID: Decodable {
static func decode(_ json: Any) throws -> BackendServerID {
return try BackendServerID(
id: json => "id",
name: json => "name"
)
}
}
Run Code Online (Sandbox Code Playgroud)
我希望能够decode用a 来打电话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)
}
}
Run Code Online (Sandbox Code Playgroud)
然后我想像这样解码对象:
XCTAssertNoThrow(try BackendServerID.decode("{\"id\": \"foo\", \"name\": \"bar\"}"))
Run Code Online (Sandbox Code Playgroud)
但是,这并不像预期的那样工作,因为某种decode(Any)方式调用该方法而不是decode(String).我究竟做错了什么?(当我通过重命名我的自定义方法来澄清调用时decodeString,它可以正常工作.)
我同意这种行为是令人惊讶的,你可能想要提出一个错误.
从快速查看CSRanking.cpp的源代码,这是类型检查器实现的一部分,它处理重载决策时不同声明的"排名" - 我们可以看到在执行:
Run Code Online (Sandbox Code Playgroud)/// \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) {
类型检查器认为具体类型中的重载比协议扩展(源)中的重载更"专用" :
Run Code Online (Sandbox Code Playgroud)// 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给出的一个:
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")
Run Code Online (Sandbox Code Playgroud)
在调用时BackendServerID.decode("foo"),具体类型BackendServerID中的重载优先于协议扩展中的BackendServerID重载(过载在具体类型的扩展中的事实在这里没有区别).在这种情况下,无论在函数签名本身是否更加专业化,这都是一样的.该位置更重要.
(虽然功能签名确实问题,如果仿制药参与-见下文切线)
值得注意的是,在这种情况下,我们可以通过在调用时强制转换方法来强制Swift使用我们想要的重载:
let str = try (BackendServerID.decode as (String) throws -> BackendServerID)("foo")
Run Code Online (Sandbox Code Playgroud)
现在,这将调用协议扩展中的重载.
如果重载都定义在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")
Run Code Online (Sandbox Code Playgroud)
类型检查器实现中的上述条件将不会被触发,因为它们都不在协议扩展中 - 因此,当涉及到重载解析时,更"专门"的重载将仅基于签名.因此,String将为String参数调用重载.
(关于通用过载的轻微切线......)
值得注意的是,类型检查器中存在(许多)其他规则,以确定一个重载是否比另一个更"专业".其中一个是优选非泛型重载到泛型重载(源):
Run Code Online (Sandbox Code Playgroud)// 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")
Run Code Online (Sandbox Code Playgroud)
该String超载现在将调用,而不是普通的一个,尽管在协议扩展之中.
实际上,正如您所看到的,有许多复杂的因素可以决定调用哪个重载.正如其他人已经说过的,在这种情况下,真正最好的解决方案是通过为您的String重载提供参数标签来明确消除重载的歧义:
extension Decodable {
static func decode(jsonString: String) throws -> Self {
// ...
}
}
// ...
let str = try BackendServerID.decode(jsonString: "{\"id\": \"foo\", \"name\": \"bar\"}")
Run Code Online (Sandbox Code Playgroud)
这不仅可以清除重载决策,还可以使API更加清晰.只是decode("someString"),不清楚字符串应该采用什么格式(XML?CSV?).现在很清楚它需要一个JSON字符串.
| 归档时间: |
|
| 查看次数: |
506 次 |
| 最近记录: |