ChatGPT解决这个技术问题 Extra ChatGPT

Swift(UI) 中的“some”关键字是什么?

新的 SwiftUI tutorial 具有以下代码:

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

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

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

关键字 some 有什么作用?

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

H
Hamish

some View 是由 SE-0244 引入的 an opaque result type,在 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 : Collection> T where T.Element == IntSee this post for more info

主要的一点是返回 some P 的函数是返回符合 P 的特定 single 具体类型的值的函数。尝试在函数中返回不同的符合类型会产生编译器错误:

// 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 的函数形成对比,后者可用于表示 both S1S2,因为它表示任意符合 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.

这是合法的,因为编译器知道 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() -> some Pfunc makeP() -> P 之间有什么区别?我已经阅读了提案,也看不出他们的样本有这种差异。
Swifts 的类型处理是一团糟。这种特殊性真的是在编译时无法处理的吗?请参阅 C# 以获取参考,它通过简单的语法隐式处理所有这些情况。 Swifts 需要有毫无意义的明确的、几乎是货物崇拜的语法,这确实使语言变得模糊。你能解释一下这个的设计原理吗? (如果您在 github 中有一个指向该提案的链接,那也很好) 编辑:刚刚注意到它链接在顶部。
@Zmaster 即使两者的实现都返回相同的具体类型,编译器也会将两种不透明的返回类型视为不同的。换句话说,选择的具体具体类型对调用者是隐藏的。 (我一直打算扩展我的答案的后半部分,以使这样的事情更加明确,但还没有解决)。
J
Jakub Truhlář

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

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

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

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

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

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

没有一些T的方式

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

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

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

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

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

animal1.isSibling(animal2) // error

这将引发错误。

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

一些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 T 让 Swift 知道正在使用 T 的什么实现,但类的用户不知道。

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


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

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

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

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

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

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

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

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


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

Hamish's answer 非常棒,从技术角度回答了这个问题。我想补充一些关于为什么在 Apple 的 SwiftUI tutorials 中的这个特定位置使用关键字 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特定 类型

文本

图片

圆圈

或实现 Viewopaque 类型,即

一些观点

通用视图

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

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

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

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

那是因为 SwiftUI 中的堆栈视图是泛型类型! 💡(列表和其他容器视图类型也是如此。)

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

VStack<TupleView<(Text, Image)>>

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

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

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

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

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

补充:

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

🔗 SwiftUI 中的“一些”是什么?


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

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

Xcode 11 release notes 是这样呈现的:

函数现在可以通过声明它遵循的协议来隐藏它们的具体返回类型,而不是指定确切的返回类型: func makeACollection() -> some Collection { return [1, 2, 3] } 调用函数的代码可以使用协议的接口,但对底层类型不可见。 (SE-0244, 40538331)

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

还要注意您可能面临的这个可能的错误:

'some' 返回类型仅适用于 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 或关联的类型要求
z
zalogatomek

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

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

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

以上是实际用法,在 some View 的 SwiftUI 中广泛使用。

但是有 一个 重要的限制 - 需要在编译时知道返回类型,所以下面再次无法给出 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()
    }
}

v
vrat2801

'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 协议的内容。它就像一个通用的协议抽象,您不必定义具体的类型。


I
Isaac L

为了简化,如果您知道两者之间的区别

var x = 5

对比

int x =5

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


L
Luc-Olivier

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

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


J
Jadian

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


d
dellos

据我了解(可能是错误的)

我有的电话

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

所以

一些协议

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


D
Divesh singh

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


S
Sardorbek Ruzmatov

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

看起来 some 与 Combine 的 eraseToAnyPublisher() 方法所做的非常接近。


A
Andy Jazz

不透明的返回类型

如果您查看我的示例,您会看到 some Gesture 意味着 myGesture 属性将始终实现 Gesture 协议,但是调用者不需要知道具体的实现类型(它是隐藏的) . body 属性也是如此——它不提供具体类型,而是根据它支持的协议来描述返回值,即 View

这是代码:

import SwiftUI

struct ContentView: View {
    
    @State private var rotate: Angle = .zero
    
    var myGesture: some Gesture {
        RotationGesture()
            .onChanged { rotate = $0 }
            .onEnded { angle in rotate = angle }
    }
    
    var body: some View {
        Rectangle()
            .frame(width: 200, height: 200)
            .foregroundColor(.blue)
            .rotationEffect(rotate)
            .gesture(myGesture)
    }
}

除了上述之外,所有应用于 Rectangle 的 SwiftUI 修饰符在返回值时也使用 some 关键字。例如:

func foregroundColor(_ color: Color?) -> some View