SwiftUIに出てくるsomeとは何なのか

iOS, Swift

TL; DR

  • someはOpaque Result Typeを表すためのキーワード
  • プロトコル型(Existential Type)と比較して、大きく2つの利点がある
    • 実行時のオーバーヘッドがない
    • 具体的な型を隠蔽できる

こんにちは。インターンシップにメンターとして参加した傍らSwiftUIを勉強しようと思っていたら、出会い頭に謎の刺客に攻撃されました。

その刺客とはそう、 someです。

分からないものを調べようとしたら別の分からないものに遭遇する感じ、久々ですね。

SwiftUIについてちゃんと理解を深めるために、まずはこの someを理解することにしました。備忘録として、そして自己の理解を深めるためのアウトプットとして残しておきます。

someとは

「とあるプロトコルを批准する何らかの型」を表すための構文です。
プロパティやメソッドの返り値の型を抽象化する(Opaque Result Type)のに用いられます。

Swift5.1から新しく追加されています。

正直これだけの情報では???という感じなので、ひとつずつ紐解いていきます。

プロトコル型(Existential Type)とジェネリクスの違い

プロトコル型とジェネリクスは、「使う側が具体的な型を知っていて、使われる(実装する)側は抽象的な型(プロトコル)を知っている」という点では同じですが、パフォーマンスの面では結構異なるようです。

  • func testHoge(hoge: Hoge) { ... }

    hogeHogeを批准するプロトコル型(Existential Type)の定数

  • func testHoge(hoge: T) { ... }

    hogeHogeを批准する何らかの型の定数

プロトコル型で変数を定義した場合、Existential Containerのラップにより、実行時に確保されるメモリの量が増えます。
しかし、ジェネリクスで変数を定義した場合、コンパイラによって型が特定されるため実行時のオーバーヘッドが発生しません。よって、パフォーマンスの面でジェネリクスに優位性があります。

Opaque Result Typeとは

プロトコル型を引数に取るメソッドのパフォーマンスをジェネリクスで改善できることがわかりました。しかし、プロトコル型の値を返すメソッドはただのジェネリクスでは改善できません。

上記の例がコンパイルエラーとなるのは、 testGenericsを使う側が決めるはずの型 Tを、実装側で Fuga型であると決め付けてしまっているためです。これを解決するのが some(Opaque Result Type)です。

この記法/構文は、コンパイル時にsome Hoge型が確定するためジェネリクスと同様のパフォーマンスを発揮することが可能でありながら、使われる側( returnHoge)は具体的な型を知っていて、使う側は抽象的な型のみを知っている というジェネリクスと正反対の関係にあります。

someの注意点

最後に someを使用する上での注意点ですが、異なる場所で someを用いて定義した変数どうしの再代入は不可能です。

上記の例では returnHogeで返される値どうしの再代入は可能です。同じメソッドの返り値である以上、内部の実装が変わっても返り値の値が同じであることが保証されるためです。

しかし、 returnHogeAlphareturnHogeBetaの両方で Fuga型の値を返していますが、その実装の外側からは some Hoge型 すなわちプロトコルHogeを批准した何らかの型としか認識されません。よって、 returnHogeBetaPiyo型の値を返すように実装を修正したときに型チェックの挙動が変わってしまうため、この場合再代入ができません。

また、条件分岐により返り値の型が変わるような実装は不可能です。これは、Opaque Result Typeの仕様上コンパイル時に生成する値の型を確定出来なければいけないからです。

さいごに

今回はちょっと内容が難しくて自分でもこれで正しく理解できているのか不安なので、もし間違い等あればコメントを頂けると幸いです…

この記事を読んでも難しいという方は参考サイトを見てみるとかなり丁寧に解説してあるのでわかりやすいのではないかと思います(というかこの記事は参考サイトの内容を端折った下位互換です)。

参考文献

この記事を書いた人

永田駿平
永田駿平
2019年新卒入社のiOSエンジニアです。
音楽とガジェットとアパレルが好きです。

iOS, Swift