SwiftUI中的some关键字是什么?

Nic*_*las 214 swift swift5.1

新的SwiftUI教程具有以下代码:

struct ContentView: View {
    var body: some View {
        Text("Hello World")
    }
}
Run Code Online (Sandbox Code Playgroud)

第二行单词some和在其网站上被高亮显示,就像它是一个关键字一样。

Swift 5.1似乎没有some作为关键字,而且我也看不出该词some还有什么其他用处,因为它可以到达类型通常所处的位置。是否有Swift的未发布新版本?以某种我不知道的方式在类型上使用的函数吗?

关键字有some什么作用?

Ham*_*ish 271

some ViewSE-0244引入的不透明结果类型,在带有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.
Run Code Online (Sandbox Code Playgroud)

不透明的结果类型是实现满足的隐式通用占位符,因此您可以考虑一下:

func bar() -> some P {
  return S1() // Implementation chooses S1 for the opaque result.
}
Run Code Online (Sandbox Code Playgroud)

看起来像这样:

func bar() -> <Output : P> Output {
  return S1() // Implementation chooses Output == S1.
}
Run Code Online (Sandbox Code Playgroud)

实际上,此功能的最终目标是允许使用更明确的形式的反向泛型,这也将使您添加约束,例如-> <T : Collection> T where T.Element == Int有关更多信息,请参见此帖子

要摆脱的主要问题是,函数返回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()
  }
}
Run Code Online (Sandbox Code Playgroud)

因为隐式通用占位符不能由多种类型满足。

这与returning函数相反P,后者可以用于表示两者 S1S2因为它表示任意P符合的值:

func baz(_ x: Int) -> P {
  if x > 10 {
    return S1()
  } else {
    return S2()
  }
}
Run Code Online (Sandbox Code Playgroud)

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


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

当前协议的主要限制是PAT(具有关联类型的协议)不能用作实际类型。尽管此限制在将来的语言版本中可能会取消,但由于不透明的结果类型实际上只是通用的占位符,因此它们现在可以与PATs一起使用。

这意味着您可以执行以下操作:

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

let collection = giveMeACollection()
print(collection.count) // 3
Run Code Online (Sandbox Code Playgroud)

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.
Run Code Online (Sandbox Code Playgroud)

这是合法的,因为编译器知道两者x并且y具有相同的具体类型。这是==两个参数类型都为的重要要求Self

protocol Equatable {
  static func == (lhs: Self, rhs: Self) -> Bool
}
Run Code Online (Sandbox Code Playgroud)

这意味着它期望两个值都与具体符合类型相同。即使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.
Run Code Online (Sandbox Code Playgroud)

由于编译器无法证明两个任意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.
Run Code Online (Sandbox Code Playgroud)

这个例子,因为虽然两者成为非法foobar回报some 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.
Run Code Online (Sandbox Code Playgroud)

如果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.
Run Code Online (Sandbox Code Playgroud)

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

此时,您可能正在考虑自己,为什么不将代码编写为:

func makeP() -> S {
  return S(i: 0)
}
Run Code Online (Sandbox Code Playgroud)

好吧,使用不透明的结果类型使您可以S通过仅公开提供的接口使类型成为实现细节P,从而使您可以灵活地在以后更改具体类型,而无需破坏任何依赖该功能的代码。

例如,您可以替换:

func makeP() -> some P {
  return S(i: 0)
}
Run Code Online (Sandbox Code Playgroud)

与:

func makeP() -> some P { 
  return T(i: 1)
}
Run Code Online (Sandbox Code Playgroud)

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

有关此功能的更多信息,请参见语言指南的“不透明类型”部分Swift进化建议

  • 无关:从Swift 5.1开始,单表达式函数中不需要`return` (15认同)
  • Swifts 的类型处理一团糟。这种特殊性真的是编译时无法处理的吗?请参阅 C# 以供参考,它通过简单的语法隐式处理所有这些情况。Swifts 需要毫无意义地明确,几乎cargo-cultist 语法真的混淆了语言。你能解释一下这个的设计原理吗?(如果你在 github 中有一个指向提案的链接也很好) 编辑:刚刚注意到它链接在顶部。 (5认同)
  • [在此处,`将需要一些P`的示例](https://tio.run/##fY7LjoJAEEX3/RV3JyQzYzSuyIwLv4CAiesWi9BJQ3W6i9HE@O2IiI@ZBbWoRaXOuTccTSmrrnOehQu2SHFW6KdsmwIF10572hmpolOCnGwZ43ONDbNVF6UKq0NAlkxS2RPBGZ6k9Q3Et4TLS5FPK/JJxXyOHbf2gIYFlf4lSGUCTAj9hylHgA6YBa4J6UzdMzxpoTQa3I/0UZ5F8c383iUN39u@5TrSCbYf2Pc7HiH99a/vfqAtCdwCP6@k@2n55/TU38RuMZjdMu66Kw) (3认同)
  • Tl;dr: `func foo&lt;T : Equatable&gt;() -&gt; T` 的返回类型是调用者选择的具体 Equatable 类型,而 `func foo() -&gt; some Equatable` 的返回类型是具体的 Equatable 类型由实施者选择的 Equatable 类型。 (2认同)
  • 但是`func makeP()-&gt; some P`和`func makeP()-&gt; P`有什么区别?我已经阅读了提案,也看不到他们的样本存在这种差异。 (2认同)
  • @Zmaster 编译器会将两个不透明的返回类型视为不同的,即使两者的实现返回相同的具体类型。换句话说,选择的特定具体类型对调用者是隐藏的。(我一直想扩展我答案的后半部分,使这样的事情更明确一点,但还没有解决)。 (2认同)

Dow*_*oat 39

另一个答案很好地解释了新some关键字的技术方面,但是该答案将尝试轻松地解释原因


假设我有一个协议Animal,并且我想比较两个动物是否是兄弟姐妹:

protocol Animal {
    func isSibling(_ animal: Self) -> Bool
}
Run Code Online (Sandbox Code Playgroud)

这样,只有在两只动物是同一种动物的情况下比较它们才是有意义的


现在让我创建一个动物的例子,仅供参考

class Dog: Animal {
    func isSibling(_ animal: Dog) -> Bool {
        return true // doesn't really matter implementation of this
    }
}
Run Code Online (Sandbox Code Playgroud)

没有的方式 some T

现在让我们说我有一个从“家庭”返回动物的函数。

func animalFromAnimalFamily() -> Animal {
    return myDog // myDog is just some random variable of type `Dog`
}
Run Code Online (Sandbox Code Playgroud)

注意:此函数实际上不会编译。这是因为在添加“某些”功能之前,如果协议使用“ Self”或泛型,则无法返回协议类型。但是,假设您可以...假装将myDog转换为抽象类型Animal,让我们看看会发生什么

现在的问题是,如果我尝试这样做:

let animal1: Animal = animalFromAnimalFamily()
let animal2: Animal = animalFromAnimalFamily()

animal1.isSibling(animal2) // error
Run Code Online (Sandbox Code Playgroud)

这将引发错误

为什么?很好的原因是,当您打电话给animal1.isSibling(animal2)Swift时,他们不知道这些动物是狗,猫还是其他动物。至于斯威夫特知道,animal1而且animal2可能是不相关的动物物种。由于我们无法比较不同类型的动物(请参见上文)。这会出错

如何some T解决这个问题

让我们重写前面的函数:

func animalFromAnimalFamily() -> some Animal {
    return myDog
}
Run Code Online (Sandbox Code Playgroud)
let animal1 = animalFromAnimalFamily()
let animal2 = animalFromAnimalFamily()

animal1.isSibling(animal2)
Run Code Online (Sandbox Code Playgroud)

animal1animal2不是 Animal 他们是类,它实现动物

现在,您可以执行以下操作:在调用时animal1.isSibling(animal2),Swift知道了animal1并且animal2是相同的类型。

所以我喜欢这样想:

some TSwift知道T正在使用什么实现,但类的用户却没有。

(自我宣传免责声明)我写了一篇博客文章,对该功能进行了更深入的介绍(与此处相同的示例)

  • 这行是我所需要的,animal1和animal2不是Animal,但是它们是实现Animal的类,现在都有意义! (5认同)
  • 所以你的想法是调用者可以利用两个调用函数返回相同类型的事实,即使调用者不知道它是什么类型? (2认同)
  • @matt 基本上是的。与字段等一起使用时的概念相同 - 向调用者保证返回类型始终是相同的类型,但不会准确揭示该类型是什么。 (2认同)

Mis*_*cha 23

Hamish的回答非常棒,并且从技术角度回答了该问题。我想对为什么some在Apple的SwiftUI教程中的这个特定位置使用关键字以及为什么要遵循它是一个好习惯进行一些思考。

some 不是必需的!

首先,您无需body的返回类型声明为不透明类型。您总是可以返回具体类型,而不是使用some View

struct ContentView: View {
    var body: Text {
        Text("Hello World")
    }
}
Run Code Online (Sandbox Code Playgroud)

这也将编译。当查看View的接口时,您会看到的返回类型body是关联的类型:

public protocol View : _View {

    /// The type of view representing the body of this view.
    ///
    /// When you create a custom view, Swift infers this type from your
    /// implementation of the required `body` property.
    associatedtype Body : View

    /// Declares the content and behavior of this view.
    var body: Self.Body { get }
}
Run Code Online (Sandbox Code Playgroud)

这意味着您可以通过body使用所选的特定类型注释属性来指定此类型。唯一的要求是该类型需要实现View协议本身。

可以是实现的特定类型,View例如

  • Text
  • Image
  • Circle

或实现的不透明类型View,即

  • some View

通用视图

当我们尝试使用堆栈视图作为body的返回类型时会出现问题,例如VStackHStack

struct ContentView: View {
    var body: VStack {
        VStack {
            Text("Hello World")
            Image(systemName: "video.fill")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这将无法编译,您将得到错误:

对通用类型'VStack'的引用需要<...>中的参数

这是因为SwiftUI中的堆栈视图是通用类型!(对于列表和其他容器视图类型也是如此。)

这很有意义,因为您可以插入任何数量的任何类型的视图(只要符合View协议)。VStack上面正文中的具体类型实际上是

VStack<TupleView<(Text, Image)>>
Run Code Online (Sandbox Code Playgroud)

当我们以后决定将视图添加到堆栈时,其具体类型会更改。如果我们在第一个文本之后添加第二个文本,我们得到

VStack<TupleView<(Text, Text, Image)>>    
Run Code Online (Sandbox Code Playgroud)

即使我们做了微小的更改,就像在文本和图像之间添加一个间隔一样细微,堆栈的类型也会改变:

VStack<TupleView<(Text, _ModifiedContent<Spacer, _FrameLayout>, Image)>>
Run Code Online (Sandbox Code Playgroud)

据我所知,就是Apple在其教程中建议始终使用some View,即所有视图都满足的最通用的不透明类型作为body返回类型的原因。您可以更改自定义视图的实现/布局,而无需每次都手动更改返回类型。


补充:

如果您想更直观地了解不透明的结果类型,我最近发表了一篇文章,可能值得一读:

SwiftUI中的“一些”是什么?

  • 这个。谢谢!Hamish的答案很完整,但是您的答案告诉我了为什么在这些示例中使用了它。 (2认同)

Cœu*_*œur 20

someSwift 5.1(swift-evolution proposal)中的关键字与Protocol一起用作返回类型。

Xcode 11 发行说明就这样表示:

现在,函数可以通过声明其遵循的协议来隐藏其具体的返回类型,而不用指定确切的返回类型:

func makeACollection() -> some Collection {
    return [1, 2, 3]
}
Run Code Online (Sandbox Code Playgroud)

调用该函数的代码可以使用协议的接口,但是看不到基础类型。(SE- 0244,40538331)

在上面的示例中,您无需告诉您将要返回Array。这样,您甚至可以返回仅符合的通用类型Collection


另请注意,您可能会遇到以下可能的错误:

“某些”返回类型仅在iOS 13.0.0或更高版本中可用

这意味着您应该使用可用性来避免some在iOS 12及更高版本上使用:

@available(iOS 13.0, *)
func makeACollection() -> some Collection {
    ...
}
Run Code Online (Sandbox Code Playgroud)

  • 正如您所指出的那样,简洁的Apple描述解释了这一切:*函数现在可以通过声明其遵循的协议来隐藏其具体的返回类型,而不是指定确切的返回类型。*然后,调用该函数的代码可以使用协议接口。整洁然后一些。 (2认同)

mat*_*att 20

我认为到目前为止缺少的所有答案some主要是在诸如DSL(特定于域的语言)之类的东西(如SwiftUI或库/框架)中有用,这会使用户(其他程序员)与您自己有所不同。

您可能永远不会some在常规应用程序代码中使用,除非它可以包装通用协议,以便可以将其用作类型(而不只是用作类型约束)。要做的some是让编译器在将超类型外观放在其前面的同时,保持其对特定类型的了解。

因此,在SwiftUI,你在哪里的用户,所有需要知道的是,什么是一个some View,而场景的所有排序的花招背后可以继续从中被屏蔽。实际上,该对象是一个非常特定的类型,但是您永远不需要了解它是什么。但是,与协议不同的是,它是完整类型,因为无论出现在哪里,它都只是某些特定完整类型的基础。

在SwiftUI的未来版本中,您可能期望some View,开发人员可以更改该特定对象的基础类型。但这不会破坏您的代码,因为您的代码最初从未提到过底层类型。

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

因此,如果您打算使用some任何东西,则很可能是正在编写供他人使用的DSL或框架/库,并且您想掩盖基础类型的详细信息。这将使您的代码更易于他人使用,并允许您在不破坏其代码的情况下更改实现细节。

但是,您也可以在自己的代码中使用它,以保护代码的一个区域免受埋在代码另一区域的实现细节的影响。

  • 我觉得这个答案(以及你在 Downgoat 的答案中的评论)是真正的答案。简短版本 - “某些”只是意味着给定的函数始终返回单个特定的具体类型(您不关心它,但符合您所做的协议)。其他答案中的示例会造成损害,兄弟示例仅在比较的“某些动物”源自相同的创建方法时才有效。 (5认同)

tza*_*oga 9

我将尝试用非常基本的实际示例来回答这个问题(这是一个不透明的结果类型是什么)

假设您有具有关联类型的协议以及实现它的两个结构:

protocol ProtocolWithAssociatedType {
    associatedtype SomeType
}

struct First: ProtocolWithAssociatedType {
    typealias SomeType = Int
}

struct Second: ProtocolWithAssociatedType {
    typealias SomeType = String
}
Run Code Online (Sandbox Code Playgroud)

在 Swift 5.1 之前,由于ProtocolWithAssociatedType can only be used as a generic constraint错误,以下内容是非法的:

func create() -> ProtocolWithAssociatedType {
    return First()
}
Run Code Online (Sandbox Code Playgroud)

但在 Swift 5.1 中这很好(some添加):

func create() -> some ProtocolWithAssociatedType {
    return First()
}
Run Code Online (Sandbox Code Playgroud)

以上是实际用法,广泛用于 SwiftUI for some View.

但有一个重要的限制 - 需要在编译时知道返回类型,因此下面的代码将不会出现Function declares an opaque return type, but the return statements in its body do not have matching underlying types错误:

func create() -> some ProtocolWithAssociatedType {
    if (1...2).randomElement() == 1 {
        return First()
    } else {
        return Second()
    }
}
Run Code Online (Sandbox Code Playgroud)


var*_*i28 5

“some”表示不透明类型。在 SwiftUI 中,View 被声明为协议

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol View {

    /// The type of view representing the body of this view.
    ///
    /// When you create a custom view, Swift infers this type from your
    /// implementation of the required `body` property.
    associatedtype Body : View

    /// Declares the content and behavior of this view.
    var body: Self.Body { get }
}
Run Code Online (Sandbox Code Playgroud)

当您将视图创建为 Struct 时,您就遵守了 View 协议,并告诉 var 主体将返回一些与 View 协议相符的内容。它就像一个通用的协议抽象,您不必定义具体的类型。