类型转换/多态性如何在 Swift 中处理这种嵌套的闭包类型?



我知道(Int) -> Void不能被类型转换为(Any) -> Void

let intHandler: (Int) -> Void = { i in
print(i)
}
var anyHandler: (Any) -> Void = intHandler <<<< ERROR

这给出了:

错误:无法将类型"(Int)-> Void"的值转换为指定类型 "(任何)->无效">


:但我不知道为什么会这样?

let intResolver: ((Int) -> Void) -> Void = { f in
f(5)
}
let stringResolver: ((String) -> Void) -> Void = { f in
f("wth")
}
var anyResolver: ((Any) -> Void) -> Void = intResolver

我弄乱了返回类型,它仍然有效...:

let intResolver: ((Int) -> Void) -> String = { f in
f(5)
return "I want to return some string here."
}
let stringResolver: ((String) -> Void) -> Void = { f in
f("wth")
}
var anyResolver: ((Any) -> Void) -> Any = intResolver (or stringResolver)

对不起,如果之前问过这个问题。我还找不到这种问题,也许我不知道这里的关键字。 请开导我!

如果您想尝试: https://iswift.org/playground?wZgwi3&v=3

这一切都与方差和 Swift 闭包有关。

Swift 在闭包返回类型方面是协变的,在参数方面是逆变的。这使得具有相同返回类型或更具体的返回类型以及相同参数或不太具体的闭包是兼容的。

因此,如果Res1: Res2Arg2: Arg1,可以将(Arg1) -> Res1分配给(Arg2) -> Res2

为了表达这一点,让我们稍微调整一下第一个闭包:

import Foundation
let nsErrorHandler: (CustomStringConvertible) -> NSError = { _ in
return NSError(domain: "", code: 0, userInfo: nil)
}
var anyHandler: (Int) -> Error = nsErrorHandler

上面的代码之所以有效,是因为Int符合CustomStringConvertible,而NSError符合ErrorAny也可以代替Error,因为它更加通用。

现在我们已经确定了这一点,让我们看看在你的两个代码块中发生了什么。

第一个块尝试将更具体的参数闭包分配给不太具体的参数闭包,这不遵循方差规则,因此它不会编译。

第二块代码怎么样?我们处于与第一个块类似的场景:带有一个参数的闭包。

  • 我们知道StringVoidAny更具体,所以我们可以将其用作返回值
  • (Int) -> Void(Any) -> Void(闭包方差规则)更具体,因此我们可以将其用作参数

考虑闭包方差,因此intResolverstringResolveranyResolver的兼容匹配。这听起来有点违反直觉,但仍然遵循编译规则,这允许分配。

然而,如果我们想使用闭包作为泛型参数,事情就会变得复杂,方差规则不再适用,这是因为 Swift 泛型(少数例外)就其类型而言是不变的:即使B: AMyGenericType<B>也不能分配给MyGenericType<A>。例外是标准库结构,如OptionalArray

首先,让我们考虑一下为什么你的第一个例子是非法的:

let intHandler: (Int) -> Void = { i in
print(i)
}
var anyHandler: (Any) -> Void = intHandler
// error: Cannot convert value of type '(Int) -> Void' to specified type '(Any) -> Void'

(Any) -> Void是可以处理任何输入的函数;(Int) -> Void只能处理Int输入的函数。因此,我们不能将Int-take函数视为可以处理任何事情的函数,因为它不能。如果我们用String称呼anyHandler怎么办?

反之亦然呢?这是合法的:

let anyHandler: (Any) -> Void = { i in
print(i)
}
var intHandler: (Int) -> Void = anyHandler

为什么?因为我们可以将处理任何东西的函数视为可以处理Int的函数,因为如果它可以处理任何东西,根据定义它必须能够处理Int

因此,我们已经确定我们可以将(Any) -> Void视为(Int) -> Void。让我们看一下你的第二个例子:

let intResolver: ((Int) -> Void) -> Void = { f in
f(5)
}
var anyResolver: ((Any) -> Void) -> Void = intResolver

为什么我们可以把((Int) -> Void) -> Void当作((Any) -> Void) -> Void?换句话说,为什么在调用anyResolver时我们可以将(Any) -> Void参数转发到(Int) -> Void参数上?好吧,正如我们已经发现的那样,我们可以将(Any) -> Void视为(Int) -> Void,因此它是合法的。

相同的逻辑适用于您的示例,((String) -> Void) -> Void

let stringResolver: ((String) -> Void) -> Void = { f in
f("wth")
}
var anyResolver: ((Any) -> Void) -> Void = stringResolver

当调用anyResolver时,我们可以将(Any) -> Void传递给它,然后将其传递给需要(String) -> VoidstringResolver。一个可以处理任何东西的函数也是一个处理字符串的函数,因此它是合法的。

使用返回类型有效:

let intResolver: ((Int) -> Void) -> String = { f in
f(5)
return "I want to return some string here."
}
var anyResolver: ((Any) -> Void) -> Any = intResolver

因为intResolver说它返回一个String,而anyResolver说它返回Any;好吧,一个字符串Any的,所以它是合法的。

最新更新