新的SwiftUI tutorial具有以下代码:

struct ContentView: View {
    var body: some View {
        Text("Hello World")
    }
}

第二行是单词some,在他们的网站上突出显示,好像它是一个关键字.

Swift 5.1似乎没有some作为关键字,我也不知道some这个词还能在那里做什么,因为它会出现在类型通常出现的地方.有没有新的、未经宣布的Swift版本?它是一个函数,以一种我不知道的方式在一个类型上使用吗?

关键词some有什么作用?

推荐答案

some ViewSE-0244引入的an opaque result type,在带有Xcode 11的Swift 5.1中可用.您可以将其视为"反向"通用占位符.

与调用者满意的常规通用占位符不同:

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.

不透明的结果类型是implementation满足的隐式泛型占位符,因此您可以这样想:

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.See this post for more info

主要的一点是,返回some P的函数是返回符合P的特定single具体类型的值的函数.try 在函数中返回不同的一致性类型会导致编译器错误:

// 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()
  }
}

因为隐式泛型占位符不能由多个类型满足.

这与返回P的函数形成对比,返回P可用于表示both S1S2,因为它表示任意的符合P的值:

func baz(_ x: Int) -> P {
  if x > 10 {
    return S1()
  } else {
    return S2()
  }
}

那么,不透明结果类型-> some P比协议返回类型-> P有什么好处呢?


1.不透明结果类型可与PAT一起使用

当前协议的一个主要限制是PAT(具有关联类型的协议)不能用作实际类型.尽管这一限制可能会在该语言的future 版本中取消,因为不透明的结果类型实际上只是泛型占位符,但它们现在可以与PAT一起使用.

这意味着你可以做以下事情:

func giveMeACollection() -> some Collection {
  return [1, 2, 3]
}

let collection = giveMeACollection()
print(collection.count) // 3

2.不透明的结果类型具有标识

由于不透明的结果类型强制返回一个具体类型,编译器知道对同一函数的两个调用必须返回同一类型的两个值.

这意味着你可以做以下事情:

//   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.

这是合法的,因为编译器知道xy具有相同的具体类型.这是==的一个重要要求,其中两个参数均为Self型.

protocol Equatable {
  static func == (lhs: Self, rhs: Self) -> Bool
}

这意味着它期望两个值都是与混凝土一致性类型相同的类型.即使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值具有相同的底层具体类型.

以类似的方式,如果我们引入另一个不透明类型返回函数:

//   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.

该示例变得非法,因为尽管foobar都返回some Equatable,但它们的"反向"通用占位符Output1Output2可以由不同的类型来满足.


3.不透明的结果类型由通用占位符组成

与常规协议类型的值不同,不透明结果类型与常规泛型占位符很好地组合,例如:

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.

如果makeP刚刚返回P,这将不起作用,因为两个P值可能具有不同的底层混凝土类型,例如:

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.

为什么在混凝土类型上使用不透明的结果类型?

此时,你可能在想,为什么不把代码写成:

func makeP() -> S {
  return S(i: 0)
}

使用不透明的结果类型可以通过只公开P提供的接口,使类型S成为一个实现细节,从而使您能够灵活地在以后更改具体类型,而不会 destruct 任何依赖于函数的代码.

例如,您可以替换:

func makeP() -> some P {
  return S(i: 0)
}

与:

func makeP() -> some P { 
  return T(i: 1)
}

不 destruct 任何调用makeP()的代码.

有关此功能的更多信息,请参见《语言指南》的the Opaque Types sectionthe Swift evolution proposal.

Swift相关问答推荐

Swift中可以取消的延迟任务

RealityKit(VisionOS)中的PhysicBodyComponent和DragGesture

SwiftUI:为什么@State在子视图中持续存在?

如何根据 DatePicker 中选定的日期实现 TabView 页面切换?

除法中如果除以完全因数需要更长时间吗?

如何在 Swift 中做类型安全的索引?

将弱引用作为类函数引用传递时,弱引用无法按预期工作

如何返回一个通过 SwiftUI 中的另一个协议间接继承 View 协议的 struct ?

如何在 swift 5.0 中获取当前行

SwiftUI 从任务中安全更新状态变量

为什么 String.contains 在我导入 Foundation 时表现不同?

如何打印出此 struct 中的数据?

如何在 Combine 合并运算符的输出上使用 eraseToAnyPublisher

如何在 Swift 中使用子定义覆盖父类扩展

无法分配给属性:absoluteString是一个只能获取的属性

What is "SwiftPM.SPMRepositoryError error 5"?

为什么'nil' 与 Swift 3 中的 'UnsafePointer' 不兼容?

如何从一个可观察的数组创建一个可观察的数组?

在 unwind segue 之后执行 push segue

如何在 Swift 中重写 setter