新的SwiftUI教程有以下代码:

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

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

Swift 5.1似乎没有把some作为关键字,而且我不知道some这个词还能在那里做什么,因为它在类型通常的位置。有没有一个新的、未公布的Swift版本?它是一个我不知道的被用在类型上的函数吗?

关键字有的作用是什么?


当前回答

some View是由SE-0244引入的不透明结果类型,在Swift 5.1和Xcode 11中可用。您可以认为这是一个“反向的”通用占位符。

不同于由调用者满足的常规通用占位符:

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:集合> T其中T. element == Int。更多信息请看这篇文章。

这里需要注意的是,返回P的函数返回的是符合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()
  }
}

因为隐式泛型占位符不能被多种类型所满足。

这与返回P的函数相反,P可以用来表示S1和S2,因为它表示一个任意P符合的值:

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

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


1. 不透明的结果类型可以与pat一起使用

目前协议的一个主要限制是pat(具有关联类型的协议)不能用作实际类型。尽管这一限制可能会在该语言的未来版本中被取消,因为不透明结果类型实际上只是泛型占位符,但它们现在可以与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.

这是合法的,因为编译器知道x和y具有相同的具体类型。这是==的一个重要要求,其中两个参数都是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.

这个例子是非法的,因为尽管foo和bar都返回一些Equatable,但它们的“反向”泛型占位符Output1和Output2可以由不同的类型满足。


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成为实现细节,从而使您可以灵活地在后面更改具体类型,而不会破坏依赖于函数的任何代码。

例如,你可以替换:

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

:

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

而不会破坏任何调用makeP()的代码。

有关此特性的进一步信息,请参阅语言指南的不透明类型部分和Swift进化建议。

其他回答

上面Mischa的帖子(抱歉,我还不能直接添加评论)指出,有些是可选的,除非你使用泛型类型,如VStack等。这是因为some是所有视图都能满足的最一般的不透明类型。因此在这里使用它有助于解决编译错误。

它看起来非常接近于Combine的eraseToAnyPublisher()方法。

你可以假设swift是通用的。

some View是由SE-0244引入的不透明结果类型,在Swift 5.1和Xcode 11中可用。您可以认为这是一个“反向的”通用占位符。

不同于由调用者满足的常规通用占位符:

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:集合> T其中T. element == Int。更多信息请看这篇文章。

这里需要注意的是,返回P的函数返回的是符合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()
  }
}

因为隐式泛型占位符不能被多种类型所满足。

这与返回P的函数相反,P可以用来表示S1和S2,因为它表示一个任意P符合的值:

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

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


1. 不透明的结果类型可以与pat一起使用

目前协议的一个主要限制是pat(具有关联类型的协议)不能用作实际类型。尽管这一限制可能会在该语言的未来版本中被取消,因为不透明结果类型实际上只是泛型占位符,但它们现在可以与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.

这是合法的,因为编译器知道x和y具有相同的具体类型。这是==的一个重要要求,其中两个参数都是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.

这个例子是非法的,因为尽管foo和bar都返回一些Equatable,但它们的“反向”泛型占位符Output1和Output2可以由不同的类型满足。


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成为实现细节,从而使您可以灵活地在后面更改具体类型,而不会破坏依赖于函数的任何代码。

例如,你可以替换:

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

:

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

而不会破坏任何调用makeP()的代码。

有关此特性的进一步信息,请参阅语言指南的不透明类型部分和Swift进化建议。

我认为,到目前为止,所有的答案都遗漏了一些,其中一些主要用于像DSL(领域特定语言)这样的东西,比如SwiftUI或库/框架,这些东西的用户(其他程序员)与你自己不同。

你可能永远不会在你的正常应用程序代码中使用一些,除非它可以包装一个泛型协议,以便它可以用作类型(而不仅仅是作为类型约束)。some所做的是让编译器保持特定类型的知识,同时在它前面放置一个超类型facade。

因此,在SwiftUI中,你是用户,你所需要知道的就是某个东西是某个视图,而在幕后,各种各样的恶作剧可以继续进行,而你是被屏蔽的。这个对象实际上是一个非常特定的类型,但您永远不需要知道它是什么。然而,与协议不同的是,它是一个成熟的类型,因为无论它出现在哪里,它都只是某个特定成熟类型的外观。

在未来版本的SwiftUI中,你期待的是某个视图,开发人员可以改变特定对象的底层类型。但这不会破坏您的代码,因为您的代码一开始就没有提到底层类型。

因此,有些人实际上使协议更像一个超类。它几乎是一种真正的对象类型,尽管不是(例如,协议的方法声明不能返回some)。

因此,如果要使用某些类型来处理任何事情,很可能是在编写DSL或框架/库供他人使用时,并且希望掩盖底层类型细节。这将使您的代码更便于他人使用,并且允许您在不破坏他人代码的情况下更改实现细节。

但是,您也可以在自己的代码中使用它,作为一种方法,将代码的一个区域与隐藏在代码的另一个区域中的实现细节隔离开来。

为了简化,如果你知道两者的区别

var x = 5

vs

int x =5

然后你就会知道一些。 编译器知道它,您也知道它。在不指定具体细节(它使用的泛型类型)的情况下,尽可能地说明您遵守了某些内容