web-dev-qa-db-ja.com

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

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

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

2行目のWord some、およびそれらのサイトでは、キーワードであるかのように強調表示されます。

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

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

240
Nicholas

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


たとえば、動物というプロトコルがあり、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」またはgenerics を使用する場合、プロトコルタイプを返すことができないためです。しかし、できるとしましょう... myDogを抽象型Animalにアップキャストするふりをして、何が起こるか見てみましょう

今私がこれをしようとすると問題が発生します:

_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_はnotAnimalbutこれらは、Animal を実装するクラスです。

これでできることは、animal1.isSibling(animal2)を呼び出すときです。Swiftは_animal1_と_animal2_が同じ型であることを知っています。

だから私はそれについて考えるのが好きな方法:

_some T_はSwiftTの実装が使用されていることを知らせますが、クラスのユーザーは知りません。

(自己プロモーションの免責事項) ブログ投稿 を書きました。これは、この新機能についてもう少し詳しく説明します(ここと同じ例)。

43
Downgoat

ハミッシュの答え は非常に素晴らしく、技術的な観点から質問に答えます。 Appleの SwiftUIチュートリアル のこの特定の場所でキーワード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 }
}

これは、youbodyプロパティに選択した特定のタイプの注釈を付けることでこのタイプを指定することを意味します。唯一の要件は、このタイプがViewプロトコル自体を実装する必要があることです。

たとえば、Viewを実装するspecificタイプのいずれかです。

  • Text
  • Image
  • Circle

またはViewを実装するopaqueタイプ、つまり.

  • some View

汎用ビュー

bodyVStackのようなHStackの戻り値の型としてスタックビューを使用しようとすると、問題が発生します。

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

これはコンパイルされず、エラーが発生します:

ジェネリック型「VStack」への参照には、<...>の引数が必要です

これは、SwiftUIのスタックビューがgenericタイプであるためです! ???? (およびListsおよび他のコンテナビュータイプについても同じことが言えます。)

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

VStack<TupleView<(Text, Image)>>

後でスタックにビューを追加することに決めたとき、その具体的なタイプが変わります。最初のテキストの後に2番目のテキストを追加すると、

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

テキストと画像の間にスペーサーを追加するような微妙な変更を加えても、スタックのタイプは変わります。

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

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


補足:

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

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

28
Mischa

これまでのすべての答えが欠けているのは、someが主にSwiftUIやライブラリ/フレームワークなどのDSL(ドメイン固有の言語)のようなもので有用であり、sers(他のプログラマー)自分とは異なる。

通常のアプリコードでsomeを使用することはないでしょう。ただし、汎用プロトコルをラップして、(型制約としてではなく)型として使用できる場合を除きます。 someが行うことは、スーパータイプファサードをその前に置いて、コンパイラに特定のタイプが何かを知らせるようにすることです。

したがって、あなたがユーザーであるSwiftUIでは、すべてのyoを知る必要があるのは、何かがsome View、舞台裏では、あらゆる種類のハンキーパンキーが、あなたがシールドされている場所から続くことができます。このオブジェクトは実際には非常に特殊なタイプですが、それが何であるかを聞く必要はありません。しかし、プロトコルとは異なり、これは本格的なタイプです。これは、どこに登場する場合でも、特定の本格的なタイプの単なる外観にすぎないためです。

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

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

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

ただし、コードのある領域を、コードの別の領域に埋もれた実装の詳細から保護する方法として、独自のコードで使用することもできます。

23
matt

Swift 5.1( Swift-evolution Proposal) )からのsomeキーワードは、戻り型としてProtocolと組み合わせて使用​​されます。

Xcode 11 リリースノート 次のように表示します。

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

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 {
    ...
}
22
Cœur

「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 bodyがView Protocolに確認するものを返すことを伝えます。これは、具体的な型を定義する必要のない一般的なプロトコル抽象化のようなものです。

1
varunrathi28

非常に基本的な実用例でこれに答えようとします(これは何ですか不透明な結果型について)

関連するタイプのプロトコルと、それを実装する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()
}

上記は実用的な使用法であり、some ViewのSwiftUIで広く使用されています。

しかし、one重要な制限があります-戻り値の型はコンパイル時に知る必要があるため、以下は再び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
tzaloga