我使用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
来澄清呼叫时,它可以正常工作。)
答案 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()
}
}
decode
中BackendServerId
的实现取代了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
。
为防止混淆,您应该以不同的方式命名接受string
和Any
的功能,例如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)
确定动态类型。