Swift(UI)の `some`キーワードとは何ですか?


259

新しいSwiftUIチュートリアルには、次のコードがあります。

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

2行目の単語some、およびサイトでは、キーワードのように強調表示されます。

Swift 5.1はsomeキーワードとして持っていないようsomeで、タイプが通常行くところに行くので、その単語が他に何をしているのかわかりません。未発表の新しいバージョンのSwiftはありますか?それは私が知らなかった方法で型で使用されている関数ですか?

キーワードsomeは何をしますか?


主題についてめまいがした人のために、ここではVadim Bulavinのおかげで非常に解読し、ステップバイステップの記事です。 vadimbulavin.com/...
リュック・オリビエ

回答:


333

some ViewSE-0244で導入された不透明な結果タイプであり、Swift 5.1 with 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 == 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()
  }
}

暗黙のジェネリックプレースホルダーは複数のタイプで満たすことができないため。

これは、返す関数とは対照的である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.不透明な結果タイプには同一性があります

不透明な結果タイプは単一の具象タイプが返されることを強制するため、コンパイラーは、同じ関数への2つの呼び出しが同じタイプの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
}

これは、どちらも具象適合型と同じ型である2つの値を期待することを意味します。Equatableタイプとして使用できたとしても、次の例のように、2つの任意の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.

コンパイラは、2つの任意の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.

2つの値の基礎となる具象型が異なる可能性があるため、が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)
}

まあ、不透明な結果型を使用すると、Sによって提供されるインターフェイスのみを公開することで、型を実装の詳細にすることができP、関数に依存するコードを壊すことなく、後で具象型を柔軟に変更できるようになります。

たとえば、次のものを置き換えることができます。

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

と:

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

を呼び出すコードを壊すことなくmakeP()

この機能の詳細については、言語ガイドの不透明タイプのセクションSwiftの進化案を参照してください。


20
関連なし:Swift 5.1以降return、単一式関数では不要
ielyamani

3
しかし:func makeP() -> some Pとの違いは何func makeP() -> Pですか?私は提案を読みましたが、彼らのサンプルについてもこの違いを見ることができません。
Artem


2
スウィフト型の処理は混乱です。この特異性は本当にコンパイル時に処理できないものですか?参照については、C#を参照してください。これは、単純な構文を通じて暗黙的にこれらすべてのケースを処理します。スウィフトは無意味に明示する必要がありますほとんどの貨物信者の構文は本当に言語を難読化しています。これの設計根拠についても説明していただけますか?(githubの提案へのリンクがあればそれも良いでしょう)編集:上部にリンクされていることに気づきました。
SacredGeometry

2
@Zmasterコンパイラは、両方の実装が同じ具象型を返す場合でも、2つの不透明な戻り値型を異なるものとして扱います。つまり、選択された特定の具象タイプは呼び出し元から隠されます。(私はこのようなことをもう少し明確にするために私の回答の後半を拡張するつもりでしたが、まだそれについては丸めていません)。
Hamish

52

もう1つの回答は、新しいsomeキーワードの技術的な側面をうまく説明していますが、この回答はその理由を簡単に説明しようとしています。


たとえば、プロトコル「動物」があり、2匹の動物が兄弟であるかどうかを比較するとします。

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

このように 、2匹の動物が同じタイプの動物である場合、兄弟であるかどうかを比較するは意味があります。


参考までに、動物の例を作成します

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を抽象型の動物に見せかけ、何が起こるか見てみましょう

今問題は私がこれをやろうとした場合です:

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

animal1.isSibling(animal2) // error

これはエラーをスローします

どうして?その理由は、animal1.isSibling(animal2)Swiftに電話をしても、動物が犬なのか猫なのかがわからないためです。Swiftが知る限り、animal1そしてanimal2関連のない動物種かもしれません。異なるタイプの動物を比較することはできません(上記を参照)。これはエラーになります

どうやって some Tこの問題を解決する

前の関数を書き直してみましょう:

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

animal1.isSibling(animal2)

animal1そして、animal2しているではない Animalしかし 、彼らはクラス以下を実装する動物です

これで今できることはanimal1.isSibling(animal2)、Swiftがそれanimal1を知っていて、animal2、同じ型であることです。

だから私がそれについて考えたい方法:

some Tすることができますスウィフトは、どのような実装を知ってT使用されているのが、クラスのユーザーにはありません。

(自己宣伝の免責事項)この新機能についてもう少し詳しく(ここと同じ例)ブログ記事を書きました


2
したがって、あなたの考えは、呼び出し元がそれがどの型であるかを知らなくても、関数への2つの呼び出しが同じ型を返すという事実を呼び出し元が利用できるということです?
マット

1
@mattは本質的にうん。フィールドなどで使用する場合も同じ概念—戻り値の型が常に同じ型であるという保証が呼び出し元に与えられますが、型が何であるかを正確に明らかにしません。
Downgoat

@Downgoatは完璧な投稿と回答をありがとうございました。some戻り値の型で理解したように、型は関数本体への制約として機能します。したがってsome、関数本体全体で1つの具象型のみを返す必要があります。例:ある場合return randomDog、他のすべての返品はでのみ機能する必要がありますDog。すべての利点はこの制約からもたらされます:の可用性animal1.isSibling(animal2)とコンパイルの利点(内部で定義されるようになったfunc animalFromAnimalFamily() -> some AnimalためSelf)。それが正しいか?
Artem

5
この行は私が必要としたすべてでした、animal1とanimal2はAnimalではありませんが、Animalを実装するクラスです。
aross

29

ハミッシュの答えはかなり素晴らしく、技術的な観点からの質問に答えます。someこのキーワードがAppleのSwiftUIチュートリアルのこの特定の場所で使用されている理由と、それがなぜ良い習慣であるのかについて考えてみます。

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のスタックビューがジェネリック型であるためです。💡(リストについても同じことが言えますや他のコンテナービュータイプです)。

Viewプロトコルに準拠している限り)任意のタイプの任意の数のビューをプラグインできるため、これは非常に理にかなっています。VStack上のボディの具体的なタイプは実際には

VStack<TupleView<(Text, Image)>>

後でビューをスタックに追加することを決定すると、その具象型が変更されます。最初のテキストの後に2番目のテキストを追加すると、

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

テキストと画像の間にスペーサーを追加するなどの微妙な変更を行っても、スタックのタイプが変更されます。

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

私が言うことができるものから、それはだ、Appleは常に使用するために彼らのチュートリアルで推奨した理由some View、すべてのビューのように、満足最も一般的な不透明なタイプbodyの戻り値の型。毎回手動で戻り値の型を変更することなく、カスタムビューの実装/レイアウトを変更できます。


補足:

不透明な結果タイプをより直感的に理解したい場合は、最近読む価値のある記事を公開しました。

S SwiftUI の「一部」とは何ですか?


2
この。ありがとう!ハミッシュの答えは非常に完全でしたが、あなたの答えは、それがこれらの例で使用されている理由を正確に教えてくれます。
Chris Marshall

「いくつか」のアイデアが大好きです。「一部」を使用すると、コンパイル時間にまったく影響するかどうかはわかりますか?
豆腐戦士

@Mischaでは、ジェネリックスビューを作成する方法を教えてください。ビューと他の動作を含むプロトコルを使用して?
theMouk

27

これまでのところすべての答えが欠けているのsomeは、SwiftUIなどのDSL(ドメイン固有の言語)やライブラリ/フレームワークなど、ユーザーがいる場合に主に役立つことだと思います(他のプログラマー)が自分とは異なる。

some一般的なプロトコルをラップして、タイプとして(タイプの制約としてではなく)使用できるようにする場合を除いて、通常のアプリのコードではおそらく使用しません。どのようなsome行いは、その前にスーパータイプのファサードを入れながら、コンパイラは、特定のタイプのものが何であるかについての知識を保つようにすることです。

したがって、ユーザーであるSwiftUIで知っおく必要があるのは、何かがであるということsome Viewだけです。このオブジェクトは実際には非常に特殊なタイプですが、それが何であるかについて聞く必要はありません。それでも、プロトコルとは異なり、それは本格的なタイプです。これは、どこに表示されても、特定の本格的なタイプの単なる外観にすぎないためです。

を期待しているSwiftUIの将来のバージョンでsome Viewは、開発者はその特定のオブジェクトの基になるタイプを変更することができます。ただし、コードが元の型について最初に言及したことがないため、コードが壊れることはありません。

したがって、some事実上、プロトコルはスーパークラスのようになります。完全ではありませんが、これはほぼ実際のオブジェクトタイプです(たとえば、プロトコルのメソッド宣言ではを返すことができませんsome)。

したがってsome、何かを使用する場合は、他の人が使用するDSLまたはフレームワーク/ライブラリを作成していて、基礎となる型の詳細をマスクする必要がある場合がほとんどです。これにより、他のユーザーが使用するコードが簡単になり、コードを壊すことなく実装の詳細を変更できるようになります。

ただし、コードの別の領域に埋め込まれた実装の詳細からコードの1つの領域をシールドする方法として、それを独自のコードで使用することもできます。


23

someSwift 5.1 のキーワード(swift-evolutionの提案)は、戻り値の型としてプロトコルと組み合わせて使用​​されます。

Xcode 11 リリースノートはそれをそのように提示します:

関数は、正確な戻り値の型を指定する代わりに、準拠するプロトコルを宣言することにより、具体的な戻り値の型を非表示にすることができます。

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

関数を呼び出すコードは、プロトコルのインターフェイスを使用できますが、基になる型を確認できません。(SE- 0244、40538331)

上記の例では、を返すことを伝える必要はありませんArray。これにより、にのみ準拠するジェネリック型を返すこともできますCollection


また、直面する可能性のあるこのエラーに注意してください。

「一部」の戻り値の型は、iOS 13.0.0以降でのみ使用できます

これは、someiOS 12以前では、可用性を使用して回避する必要があることを意味します。

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

1
この焦点を絞った回答とXcode 11ベータのコンパイラー問題に感謝します
ブレインレイ

1
someiOS 12以前では回避するために可用性を使用することになっています。あなたがしている限り、あなたは大丈夫です。問題は、コンパイラーがこれを行うように警告しないことだけです。
マット

2
クール、あなたが指摘するように、簡潔なAppleの説明はそれをすべて説明します:関数は、正確な戻り値の型を指定する代わりに、準拠するプロトコルを宣言することにより、具体的な戻り値の型を隠すことができます。 そして、関数を呼び出すコードは、プロトコルインターフェイスを使用できます。きちんとした、そしていくつか。
Fattie

これ(具体的な戻り値の型を非表示にすること)は、キーワード "some"を使用しなくても既に可能です。メソッドシグネチャに「一部」を追加する効果については説明していません。
ビンスオサリバン

@ VinceO'Sullivan someSwift 5.0またはSwift 4.2では、このコードサンプルのキーワードを削除することはできません。エラーは次のようになります。「それは自己または関連するタイプの要件があるため、議定書『コレクションは、』唯一の一般的な制約として使用することができます
・クール

2

「一部」は不透明なタイプを意味します。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 bodyがViewプロトコルに確認する何かを返すことを伝えます。具体的なタイプを定義する必要がない一般的なプロトコルの抽象化に似ています。


2

私はこれを非常に基本的な実用的な例で答えようとします(これは不透明な結果タイプとは何ですか

関連付けられたタイプのプロトコルがあり、それを実装する2つの構造体があるとします。

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 forで広く使用されていsome Viewます。

ただし、重要な制限が1つあります。戻り値の型はコンパイル時に認識される必要があるため、以下では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()
    }
}

0

頭に浮かぶ簡単なユースケースは、数値型の汎用関数を書くことです。

/// Adds one to any decimal type
func addOne<Value: FloatingPoint>(_ x: Value) -> some FloatingPoint {
    x + 1
}

// Variables will be assigned 'some FloatingPoint' type
let double = addOne(Double.pi) // 4.141592653589793
let float = addOne(Float.pi) // 4.141593

// Still get all of the required attributes/functions by the FloatingPoint protocol
double.squareRoot() // 2.035090330572526
float.squareRoot() // 2.03509

// Be careful, however, not to combine 2 'some FloatingPoint' variables
double + double // OK 
//double + float // error

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.