新しい SwiftUIチュートリアル には次のコードがあります。
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
2行目のWord some
、およびそれらのサイトでは、キーワードであるかのように強調表示されます。
Swift 5.1にはキーワードとしてsome
が含まれていないようです。また、Word some
が他に何をしているのかはわかりません。 Swiftの新しい未発表バージョンはありますか?それは私が知らなかった方法で型で使用されている関数ですか?
キーワードsome
は何をしますか?
もう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
_はnotAnimal
、butこれらは、Animal を実装するクラスです。
これでできることは、animal1.isSibling(animal2)
を呼び出すときです。Swiftは_animal1
_と_animal2
_が同じ型であることを知っています。
だから私はそれについて考えるのが好きな方法:
_
some T
_はSwiftにT
の実装が使用されていることを知らせますが、クラスのユーザーは知りません。
(自己プロモーションの免責事項) ブログ投稿 を書きました。これは、この新機能についてもう少し詳しく説明します(ここと同じ例)。
ハミッシュの答え は非常に素晴らしく、技術的な観点から質問に答えます。 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
body
やVStack
のような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
の戻り値の型。毎回戻り値の型を手動で変更することなく、カスタムビューの実装/レイアウトを変更できます。
不透明な結果の種類をより直感的に理解したい場合は、最近読む価値のある記事を公開しました。
これまでのすべての答えが欠けているのは、some
が主にSwiftUIやライブラリ/フレームワークなどのDSL(ドメイン固有の言語)のようなもので有用であり、sers(他のプログラマー)自分とは異なる。
通常のアプリコードでsome
を使用することはないでしょう。ただし、汎用プロトコルをラップして、(型制約としてではなく)型として使用できる場合を除きます。 some
が行うことは、スーパータイプファサードをその前に置いて、コンパイラに特定のタイプが何かを知らせるようにすることです。
したがって、あなたがユーザーであるSwiftUIでは、すべてのyoを知る必要があるのは、何かがsome View
、舞台裏では、あらゆる種類のハンキーパンキーが、あなたがシールドされている場所から続くことができます。このオブジェクトは実際には非常に特殊なタイプですが、それが何であるかを聞く必要はありません。しかし、プロトコルとは異なり、これは本格的なタイプです。これは、どこに登場する場合でも、特定の本格的なタイプの単なる外観にすぎないためです。
SwiftUIの将来のバージョンでは、some View
、開発者はその特定のオブジェクトの基本型を変更できます。ただし、コードが最初に元の型に言及することはないため、コードが破損することはありません。
したがって、some
は事実上、プロトコルをよりスーパークラスのようにします。 ほぼ実際のオブジェクト型ですが、完全ではありません(たとえば、プロトコルのメソッド宣言はsome
を返すことができません)。
したがって、some
を何かに使用する場合は、yoが他の人が使用するDSLまたはフレームワーク/ライブラリを作成していて、基礎となる型をマスクしたい場合がほとんどです。詳細。これにより、他の人が使用するコードが簡単になり、コードを壊さずに実装の詳細を変更できます。
ただし、コードのある領域を、コードの別の領域に埋もれた実装の詳細から保護する方法として、独自のコードで使用することもできます。
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 {
...
}
「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に確認するものを返すことを伝えます。これは、具体的な型を定義する必要のない一般的なプロトコル抽象化のようなものです。
非常に基本的な実用例でこれに答えようとします(これは何ですか不透明な結果型について)
関連するタイプのプロトコルと、それを実装する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()
}
}