我知道(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: Res2
和Arg2: 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
符合Error
。Any
也可以代替Error
,因为它更加通用。
现在我们已经确定了这一点,让我们看看在你的两个代码块中发生了什么。
第一个块尝试将更具体的参数闭包分配给不太具体的参数闭包,这不遵循方差规则,因此它不会编译。
第二块代码怎么样?我们处于与第一个块类似的场景:带有一个参数的闭包。
- 我们知道
String
或Void
比Any
更具体,所以我们可以将其用作返回值 (Int) -> Void
比(Any) -> Void
(闭包方差规则)更具体,因此我们可以将其用作参数
考虑闭包方差,因此intResolver
和stringResolver
是anyResolver
的兼容匹配。这听起来有点违反直觉,但仍然遵循编译规则,这允许分配。
然而,如果我们想使用闭包作为泛型参数,事情就会变得复杂,方差规则不再适用,这是因为 Swift 泛型(少数例外)就其类型而言是不变的:即使B: A
,MyGenericType<B>
也不能分配给MyGenericType<A>
。例外是标准库结构,如Optional
和Array
。
首先,让我们考虑一下为什么你的第一个例子是非法的:
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) -> Void
stringResolver
。一个可以处理任何东西的函数也是一个处理字符串的函数,因此它是合法的。
使用返回类型有效:
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
的,所以它是合法的。