新的SwiftUI教程具有以下代码:
struct ContentView: View { var body: some View { Text("Hello World") } }
第二行单词some和在其网站上被高亮显示,就像它是一个关键字一样。
some
Swift 5.1似乎没有some作为关键字,而且我也看不出该词some还有什么其他用处,因为它通常位于类型所在的位置。是否有Swift的未发布新版本?以某种我不知道的方式在类型上使用的函数吗?
关键字有some什么作用?
some View是SE-0244引入的不透明结果类型,在带有Xcode 11的Swift 5.1中可用。您可以将其视为“反向”通用占位符。
some View
与调用方可以满足的常规通用占位符不同:
protocol P {} struct S1 : P {} struct S2 : P {} func foo<T : P>(_ x: T) {} foo(S1()) // Caller chooses T == S1. foo(S2()) // Caller chooses T == S2.
不透明的结果类型是 实现 满足的隐式通用占位符,因此您可以考虑一下:
func bar() -> some P { return S1() // Implementation chooses S1 for the opaque result. }
看起来像这样:
func bar() -> <Output : P> Output { return S1() // Implementation chooses Output == S1. }
实际上,此功能的最终目标是允许以更明确的形式使用反向泛型,这也将使您添加约束,例如-> <T : Collection> T where T.Element == Int。有关更多信息,请参见此帖子。
-> <T : Collection> T where T.Element == Int
要摆脱的主要问题是,函数返回some P是一个函数,该函数返回符合的特定 单个 具体类型的值P。尝试在函数中返回不同的符合类型会产生编译器错误:
some P
P
// error: Function declares an opaque return type, but the return // statements in its body do not have matching underlying types. func bar(_ x: Int) -> some P { if x > 10 { return S1() } else { return S2() } }
因为隐式通用占位符不能由多种类型满足。
这与returning函数相反P,后者可以用于表示 两者 S1和S2因为它表示任意P符合的值:
S1
S2
func baz(_ x: Int) -> P { if x > 10 { return S1() } else { return S2() } }
好的,不透明结果类型-> some P比协议返回类型有什么好处-> P?
-> some P
-> P
当前协议的主要限制是PAT(具有关联类型的协议)不能用作实际类型。尽管此限制在将来的语言版本中可能会取消,但由于不透明的结果类型实际上只是通用的占位符,因此今天它们可以与PATs一起使用。
这意味着您可以执行以下操作:
func giveMeACollection() -> some Collection { return [1, 2, 3] } let collection = giveMeACollection() print(collection.count) // 3
由于不透明的结果类型强制返回单个具体类型,因此编译器知道对同一函数的两次调用必须返回相同类型的两个值。
// foo() -> <Output : Equatable> Output { func foo() -> some Equatable { return 5 // The opaque result type is inferred to be Int. } let x = foo() let y = foo() print(x == y) // Legal both x and y have the return type of foo.
这是合法的,因为编译器知道两者x并且y具有相同的具体类型。这是==两个参数类型都为的重要要求Self。
x
y
==
Self
protocol Equatable { static func == (lhs: Self, rhs: Self) -> Bool }
这意味着它期望两个值都与具体符合类型相同。即使Equatable可以用作类型,也无法将两个任意Equatable符合的值相互比较,例如:
Equatable
func foo(_ x: Int) -> Equatable { // Assume this is legal. if x > 10 { return 0 } else { return "hello world" } } let x = foo(20) let y = foo(5) print(x == y) // Illegal.
由于编译器无法证明两个任意Equatable值具有相同的基础具体类型。
In a similar manner, if we introduced another opaque type returning function:
// foo() -> <Output1 : Equatable> Output1 { func foo() -> some Equatable { return 5 // The opaque result type is inferred to be Int. } // bar() -> <Output2 : Equatable> Output2 { func bar() -> some Equatable { return "" // The opaque result type is inferred to be String. } let x = foo() let y = bar() print(x == y) // Illegal, the return type of foo != return type of bar.
The example becomes illegal because although both foo and bar return some Equatable, their “reverse” generic placeholders Output1 and Output2 could be satisfied by different types.
foo
bar
some Equatable
Output1
Output2
Unlike regular protocol-typed values, opaque result types compose well with regular generic placeholders, for example:
protocol P { var i: Int { get } } struct S : P { var i: Int } func makeP() -> some P { // Opaque result type inferred to be S. return S(i: .random(in: 0 ..< 10)) } func bar<T : P>(_ x: T, _ y: T) -> T { return x.i < y.i ? x : y } let p1 = makeP() let p2 = makeP() print(bar(p1, p2)) // Legal, T is inferred to be the return type of makeP.
This wouldn’t have worked if makeP had just returned P, as two P values may have different underlying concrete types, for example:
makeP
struct T : P { var i: Int } func makeP() -> P { if .random() { // 50:50 chance of picking each branch. return S(i: 0) } else { return T(i: 1) } } let p1 = makeP() let p2 = makeP() print(bar(p1, p2)) // Illegal.
At this point you may be thinking to yourself, why not just write the code as:
func makeP() -> S { return S(i: 0) }
Well, the use of an opaque result type allows you to make the type S an implementation detail by exposing only the interface provided by P, giving you flexibility of changing the concrete type later down the line without breaking any code that depends on the function.
S
For example, you could replace:
func makeP() -> some P { return S(i: 0) }
with:
func makeP() -> some P { return T(i: 1) }
without breaking any code that calls makeP().
makeP()
See the Opaque Types section of the language guide and the Swift evolution proposal for further information on this feature.