SwiftUI ForEach类型不匹配无编译错误?类型推断机制解析
问题描述
1. ForEach闭包类型匹配疑问
以下代码可正常编译无警告,但存在疑问:
struct ContentView: View { var lists = ["a", "b"] var body: some View { VStack { ForEach(lists, id: \.self) { (str: String?) in if let str { Text(str) } } } } }
lists是[String],符合RandomAccessCollection协议,其关联类型Element为String。按常理ForEach应该只接受(String) -> some View类型的闭包,但这里传入(String?) -> some View却能编译通过,原因是什么?
2. RandomAccessCollection的Element类型疑问
查看RandomAccessCollection的定义时,似乎存在两个Element类型:一个看起来像泛型参数,另一个是协议的关联类型,二者是同一类型还是不同类型?
解答
1. 闭包的自动子类型转换
这是Swift的函数类型子类型转换机制导致的,核心规则是:
函数类型
T1->T2是T3->T4的子类型,当且仅当T3是T1的子类型(参数逆变),且T2是T4的子类型(返回值协变),同时任何类型都是自身的子类型。
此外,Swift中默认将非可选类型T视为可选类型T?的子类型(非可选值可自动包装为可选值,符合直觉但文档未明确标注)。
回到示例代码:
ForEach期望的闭包类型是(String) -> some View- 传入的闭包类型是
(String?) -> some View
根据规则:
- 参数部分:
String是String?的子类型,满足参数逆变要求(函数参数可以接受更宽泛的类型) - 返回值部分:
some View与some View类型一致,满足协变要求
因此(String) -> some View是(String?) -> some View的子类型,传入的闭包可以被ForEach正常接受。
再看一个验证示例,这段代码同样可以正常编译:
struct Solution { lazy var fun1: (Int) -> Any = fun2 var fun2: (Any) -> Int }
这里fun2的类型(Any)->Int是fun1类型(Int)->Any的子类型:
- 参数部分:
Int是Any的子类型,满足逆变要求 - 返回值部分:
Int是Any的子类型,满足协变要求
2. RandomAccessCollection的Element类型说明
RandomAccessCollection并没有两个独立的Element类型,你看到的“泛型参数”其实是Swift的语法糖写法。
RandomAccessCollection是一个协议,仅定义了关联类型Element,用于表示集合中元素的类型。当你看到RandomAccessCollection<Element>这种写法时,这是Swift用来明确指代“关联类型为Element的RandomAccessCollection协议实现”的语法糖,和协议内部的关联类型是同一个概念。
比如Array<String>符合RandomAccessCollection,它的关联类型Element就是String,因此可以说Array<String>是RandomAccessCollection<String>的一个具体实现。
内容的提问来源于stack exchange,提问作者user605591

