【问题标题】:What is the "some" keyword in Swift(UI)?Swift(UI) 中的“some”关键字是什么?
【发布时间】:2019-10-19 09:08:19
【问题描述】:

新的SwiftUI tutorial 代码如下:

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

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

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

关键字some 有什么作用?

【问题讨论】:

  • 对于那些被这个主题头晕目眩的人,这里有一篇非常解密和一步一步的文章,感谢 Vadim Bulavin。 vadimbulavin.com/…

标签: swift swiftui swift5


【解决方案1】:

some View 是由SE-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.

不透明的结果类型是由实现满足的隐式通用占位符,所以你可以这样想:

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

看起来像这样:

func bar() -> <Output : P> Output {
  return S1() // Implementation chooses Output == S1.
}

事实上,这个特性的最终目标是允许反向泛型以这种更明确的形式出现,这也可以让你添加约束,例如-&gt; &lt;T : Collection&gt; T where T.Element == IntSee this post for more info.

主要的一点是返回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()
  }
}

由于隐式泛型占位符不能满足多种类型。

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

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

好的,那么不透明结果类型-&gt; some P 相对于协议返回类型-&gt; 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.

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

例如,您可以替换:

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

与:

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

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

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

【讨论】:

  • @ielyamani 是的,虽然我个人更喜欢保留 return 函数声明。也许这只是惯性,但如果没有return 对我来说,它的某些东西看起来很奇怪。不过,我确实喜欢从计算属性中省略 return
  • 但是func makeP() -&gt; some Pfunc makeP() -&gt; P之间有什么区别?我已经阅读了提案,也看不出他们的样本有这种差异。
  • Swifts 的类型处理是一团糟。这种特殊性真的是在编译时无法处理的吗?请参阅 C# 以获取参考,它通过简单的语法隐式处理所有这些情况。 Swifts 需要有毫无意义的明确的几乎是货物崇拜的语法,这确实使语言变得模糊。你能解释一下这个的设计原理吗? (如果你在 github 上有一个提案的链接,那也很好)编辑:刚刚注意到它链接在顶部。
  • @Zmaster 编译器会将两种不透明的返回类型视为不同的,即使两者的实现都返回相同的具体类型。换句话说,选择的具体具体类型对调用者是隐藏的。 (我一直打算扩展我的答案的后半部分,以使这样的事情更加明确,但还没有解决)。
【解决方案2】:

另一个答案很好地解释了新的some 关键字的技术方面,但这个答案将尝试轻松解释为什么


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

protocol Animal {
    func isSibling(_ animal: Self) -> Bool
}

这样只有在两个动物是同一类型的动物时比较它们是否是兄弟姐妹才有意义


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

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

没有some T的路

现在假设我有一个从“家庭”返回动物的函数。

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

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

现在问题来了,如果我尝试这样做:

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

animal1.isSibling(animal2) // error

这会引发错误

为什么?原因是,当你打电话给animal1.isSibling(animal2) 时,Swift 不知道这些动物是狗、猫还是什么。 据 Swift 所知,animal1animal2 可能是不相关的动物物种。因为我们无法比较不同类型的动物(见上文)。这会出错

some T如何解决这个问题

让我们重写之前的函数:

func animalFromAnimalFamily() -> some Animal {
    return myDog
}
let animal1 = animalFromAnimalFamily()
let animal2 = animalFromAnimalFamily()

animal1.isSibling(animal2)

animal1animal2 不是 Animal但是 它们是实现 Animal 的类

这让你现在可以做的是,当你调用 animal1.isSibling(animal2) 时,Swift 知道 animal1animal2 是同一类型。

所以我喜欢这样思考:

some TSwift 知道正在使用 T 的什么实现,但该类的用户不知道。

(自我推销免责声明)我写了一个blog post,对这个新功能进行了更深入的介绍(与此处相同的示例)

【讨论】:

  • 所以你的想法是调用者可以利用两个调用函数返回相同类型的事实,即使调用者不知道它是什么类型?
  • @matt 基本上是的。与字段等一起使用时的相同概念 - 向调用者保证返回类型将始终是相同的类型,但不会准确显示类型是什么。
  • @Downgoat 非常感谢您提供完美的帖子和回答。据我了解,返回类型中的some 是对函数体的约束。所以some 需要在整个函数体中只返回一个具体类型。例如:如果有return randomDog,那么所有其他返回必须仅适用于Dog。所有好处都来自这个约束:animal1.isSibling(animal2) 的可用性和func animalFromAnimalFamily() -&gt; some Animal 编译的好处(因为现在Self 已在后台定义)。对吗?
  • 这一行是我所需要的,animal1和animal2不是Animal,但它们是实现Animal的类,现在一切都有意义了!
  • 你的例子很奇怪。如果“aminalFromAnimalFamiky”方法应该从一个家庭中创建动物,为什么它会产生更通用的动物?)你创造了这个问题,你解决了它))
【解决方案3】:

Hamish's answer 非常棒,从技术角度回答了这个问题。我想补充一些想法,为什么在 Apple 的 SwiftUI tutorials 的这个特定位置使用关键字 some,以及为什么这是一个很好的做法。

some 不是必需的!

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

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

这也将编译。当您查看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 }
}

这意味着通过使用您选择的特定类型注释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")
        }
    }
}

这不会编译,你会得到错误:

对泛型“VStack”的引用需要<...>

中的参数

这是因为 SwiftUI 中的堆栈视图是 generic 类型! ?(Lists 和其他容器视图类型也是如此。)

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

VStack<TupleView<(Text, Image)>>

当我们稍后决定将视图添加到堆栈时,它的具体类型会发生变化。如果我们在第一个文本之后添加第二个文本,我们会得到

VStack<TupleView<(Text, Text, Image)>>    

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

VStack<TupleView<(Text, _ModifiedContent<Spacer, _FrameLayout>, Image)>>

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


补充:

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

?What’s this “some” in SwiftUI?

【讨论】:

  • 这个。谢谢! Hamish 的回答非常完整,但你的回答确切地告诉了我为什么在这些示例中使用它。
  • 我喜欢“一些”的概念。知道使用“some”是否会影响编译时间吗?
  • @Mischa 那么如何制作泛型视图?使用包含视图和其他行为的协议?
【解决方案4】:

我认为到目前为止所有答案都缺少的是 some 主要在诸如 SwiftUI 或库/框架之类的 DSL(域特定语言)之类的东西中很有用,它们将拥有 用户 em>(其他程序员)与你不同。

你可能永远不会在你的普通应用程序代码中使用some,除非它可以包装一个通用协议,以便它可以用作类型(而不仅仅是作为类型约束)。 some 所做的是让编译器知道某物是什么特定类型,同时在其前面放置一个超类型外观。

因此,在 SwiftUI 中,您是用户,所有需要知道的是some View,而在幕后,您可以继续使用各种手帕被屏蔽。这个对象实际上是一个非常特殊的类型,但你永远不需要知道它是什么。然而,与协议不同的是,它是一种成熟的类型,因为无论它出现在哪里,它都只是某些特定的成熟类型的外观。

在 SwiftUI 的未来版本中,您需要 some View,开发人员可以更改该特定对象的底层类型。但这不会破坏你的代码,因为你的代码从一开始就没有提到底层类型。

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

因此,如果您打算将some 用于任何事情,很可能是正在编写供其他人使用的 DSL 或框架/库,并且您想屏蔽底层类型细节。这将使您的代码更易于其他人使用,并允许您在不破坏他们的代码的情况下更改实现细节。

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

【讨论】:

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

Swift 5.1 (swift-evolution proposal) 中的 some 关键字与协议一起用作返回类型。

Xcode 11 release notes 这样呈现:

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

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

调用该函数的代码可以使用协议的接口,但无法看到底层类型。 (SE-0244, 40538331)

在上面的示例中,您无需告诉您将返回Array。这甚至允许您返回仅符合 Collection 的泛型类型。


还要注意您可能遇到的这个可能的错误:

“某些”返回类型仅适用于 iOS 13.0.0 或更高版本

这意味着您应该在 iOS 12 及之前使用可用性来避免 some

@available(iOS 13.0, *)
func makeACollection() -> some Collection {
    ...
}

【讨论】:

  • 非常感谢这个集中的答案和 Xcode 11 beta 中的编译器问题
  • 您应该使用可用性来避免在 iOS 12 及之前版本上使用some。只要你这样做,你应该没问题。问题只是编译器没有警告你这样做。
  • Cœur,正如您所指出的,简洁的 Apple 描述说明了一切:函数现在可以通过声明它所遵循的协议来隐藏其具体的返回类型,而不是指定确切的返回类型. 然后调用函数的代码就可以使用协议接口了。整洁,然后一些。
  • 这(隐藏具体的返回类型)已经可以不使用关键字“some”。它没有解释在方法签名中添加“一些”的效果。
  • @VinceO'Sullivan 在 Swift 5.0 或 Swift 4.2 中无法删除此给定代码示例中的 some 关键字。错误将是:“Protocol 'Collection' 只能用作通用约束,因为它具有 Self 或关联的类型要求
【解决方案6】:

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

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

protocol ProtocolWithAssociatedType {
    associatedtype SomeType
}

struct First: ProtocolWithAssociatedType {
    typealias SomeType = Int
}

struct Second: ProtocolWithAssociatedType {
    typealias SomeType = String
}

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

func create() -> ProtocolWithAssociatedType {
    return First()
}

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

func create() -> some ProtocolWithAssociatedType {
    return First()
}

以上是实际用法,在 SwiftUI 中广泛用于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()
    }
}

【讨论】:

    【解决方案7】:

    '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 }
    }
    

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

    【讨论】:

      【解决方案8】:

      为了简化,如果你知道它们之间的区别

      var x = 5
      

      int x =5
      

      那么你就会知道some。 编译器知道,你也知道。在不指定细节(它使用的通用类型)的情况下说你遵守某事的最小努力

      【讨论】:

        【解决方案9】:

        对于那些被这个主题头晕的人,这里感谢 Vadim Bulavin,这是一篇非常解密和逐步的文章。

        https://www.vadimbulavin.com/opaque-return-types-and-the-some-keyword-in-swift/

        【讨论】:

          【解决方案10】:

          简单的理解方式,比如 Objc 中的kindOf

          【讨论】:

            【解决方案11】:

            在我的理解(可能是错误的)

            我的电话

            Protocol View{}
            
             class Button: View { // subclass of View } 
            
             //this class not a subclass of View
             class ButtonBuilder<T> where T:View { //using T as View here   } 
            

            然后

            var body: View = Button() // ok
            var body: View = ButtonBilder() //not ok
            var body: some View = ButtonBilder() //ok
            

            所以

            一些协议

            可以在自己的代码中将使用该协议的泛型类视为该协议的子类

            【讨论】:

              【解决方案12】:

              您可以在 swift 中假设为泛型。

              【讨论】:

                【解决方案13】:

                Mischa 的上述帖子(抱歉,我还不能直接添加评论)指出 some 是可选的,除非您使用泛型类型作为 VStack 等。那是因为 some 是所有视图中最通用的不透明类型满足。所以在这里使用它有助于解决编译错误。

                似乎some 与Combine 的eraseToAnyPublisher() 方法的作用非常接近。

                【讨论】:

                  猜你喜欢
                  • 2016-11-07
                  • 2016-12-21
                  • 1970-01-01
                  • 2018-05-05
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-05-15
                  • 2017-01-24
                  • 2014-11-29
                  相关资源
                  最近更新 更多