TL; DR
- someはOpaque Result Typeを表すためのキーワード
- プロトコル型(Existential Type)と比較して、大きく2つの利点がある
- 実行時のオーバーヘッドがない
- 具体的な型を隠蔽できる
こんにちは。インターンシップにメンターとして参加した傍らSwiftUIを勉強しようと思っていたら、出会い頭に謎の刺客に攻撃されました。
その刺客とはそう、 someです。
struct MyView: View { var body: some View { // こいつ Text("Hello World") } } |
分からないものを調べようとしたら別の分からないものに遭遇する感じ、久々ですね。
SwiftUIについてちゃんと理解を深めるために、まずはこの someを理解することにしました。備忘録として、そして自己の理解を深めるためのアウトプットとして残しておきます。
someとは
「とあるプロトコルを批准する何らかの型」を表すための構文です。
プロパティやメソッドの返り値の型を抽象化する(Opaque Result Type)のに用いられます。
Swift5.1から新しく追加されています。
// プロトコルHogeを批准する何らかの型の変数hoge var hoge: some Hoge = Fuga() // プロトコルHogeを批准する何らかの型のインスタンスを返すメソッド func returnHoge() -> some Hoge { return Fuga() } |
正直これだけの情報では???という感じなので、ひとつずつ紐解いていきます。
プロトコル型(Existential Type)とジェネリクスの違い
プロトコル型とジェネリクスは、「使う側が具体的な型を知っていて、使われる(実装する)側は抽象的な型(プロトコル)を知っている」という点では同じですが、パフォーマンスの面では結構異なるようです。
-
func testHoge(hoge: Hoge) { ... }
→ hogeは Hogeを批准するプロトコル型(Existential Type)の定数
-
func testHoge(hoge: T) { ... }
→ hogeはHogeを批准する何らかの型の定数
プロトコル型で変数を定義した場合、Existential Containerのラップにより、実行時に確保されるメモリの量が増えます。
しかし、ジェネリクスで変数を定義した場合、コンパイラによって型が特定されるため実行時のオーバーヘッドが発生しません。よって、パフォーマンスの面でジェネリクスに優位性があります。
Opaque Result Typeとは
プロトコル型を引数に取るメソッドのパフォーマンスをジェネリクスで改善できることがわかりました。しかし、プロトコル型の値を返すメソッドはただのジェネリクスでは改善できません。
// コンパイルエラーが発生する func returnHoge<T: Hoge>() -> T { return Fuga() } |
上記の例がコンパイルエラーとなるのは、 testGenericsを使う側が決めるはずの型 Tを、実装側で Fuga型であると決め付けてしまっているためです。これを解決するのが some(Opaque Result Type)です。
func returnHoge() -> some Hoge { return Fuga() } |
この記法/構文は、コンパイル時にsome Hoge型が確定するためジェネリクスと同様のパフォーマンスを発揮することが可能でありながら、使われる側( returnHoge)は具体的な型を知っていて、使う側は抽象的な型のみを知っている というジェネリクスと正反対の関係にあります。
someの注意点
最後に someを使用する上での注意点ですが、異なる場所で someを用いて定義した変数どうしの再代入は不可能です。
func returnHoge() -> some Hoge { return Fuga() } var hoge1 = returnHoge() var hoge2 = returnHoge() hoge1 = hoge2 // OK func returnHogeAlpha() -> some Hoge { return Fuga() } func returnHogeBeta() -> some Hoge { return Fuga() } var hogeAlpha = returnHogeAlpha() var hogeBeta = returnHogeBeta() hogeAlpha = hogeBeta // コンパイルエラー |
上記の例では returnHogeで返される値どうしの再代入は可能です。同じメソッドの返り値である以上、内部の実装が変わっても返り値の値が同じであることが保証されるためです。
しかし、 returnHogeAlphaと returnHogeBetaの両方で Fuga型の値を返していますが、その実装の外側からは some Hoge型 すなわちプロトコルHogeを批准した何らかの型としか認識されません。よって、 returnHogeBetaで Piyo型の値を返すように実装を修正したときに型チェックの挙動が変わってしまうため、この場合再代入ができません。
また、条件分岐により返り値の型が変わるような実装は不可能です。これは、Opaque Result Typeの仕様上コンパイル時に生成する値の型を確定出来なければいけないからです。
func returnHoge(isFuga: Bool) -> some Hoge { // some HogeがFugaなのかPiyoなのかがコンパイル時に確定しないのでエラー return isFuga ? Fuga() : Piyo() } |
さいごに
今回はちょっと内容が難しくて自分でもこれで正しく理解できているのか不安なので、もし間違い等あればコメントを頂けると幸いです…
この記事を読んでも難しいという方は参考サイトを見てみるとかなり丁寧に解説してあるのでわかりやすいのではないかと思います(というかこの記事は参考サイトの内容を端折った下位互換です)。
参考文献
- Swift 5.1 に導入される Opaque Result Type とは何か
- SwiftUI を理解するために必要な Swift 5.1 の新機能 (some View編)
- Reverse generics and opaque result types
この記事を書いた人
-
iOSアプリを作っています
音楽とガジェットが好きです
最近書いた記事
- 2023.12.11【iOS】Kotlin MultiplatformでApp Clip対応アプリを2か月で作ったので振り返る
- 2023.09.01【iOS】SwiftUIとCoreImageを使って、EXIF情報を表示した画像をシェアする
- 2022.12.24Swift 5.7の新機能を用いて、複数サービスを扱うアプリをリアーキテクチャする
- 2021.05.31SwiftUIに出てくるsomeとは何なのか