SwiftUIでViewを作る時、以下のようにsomeというキーワードが出てきます。
今回はこのキーワードについて調べてみました。
struct ContentView: View { var body: some View { Text("Hello, World!") } }
調査にあたって以下の記事を参考にさせて頂きました。
Swift 5.1 に導入される Opaque Result Type とは何か - Qiita
someキーワードですが、実行時のパフォーマンス向上の為のキーワードになります。
具体的には「戻り値の型をプロトコルとして宣言するとメモリ使用量が増える」という悩みを解消してくれるものになります。
仮に下のようなクラスとプロトコルがあるとします。
protocol MyProtocol { } class MyClass: MyProtocol { }
その時、下の2つのメソッドを比べるとMyProtocolを戻り値の型にしたほうがメモリ使用量が大きくなってしまいます。
// メモリ使用量が大きい func myProtocol() -> MyProtocol { return MyClass() } // メモリ使用量が小さい func myProtocol() -> MyClass { return MyClass() }
それを解決するのがsomeキーワードです。
someを付ける事で、メソッドの実装によって戻り値の型が最適なものになります。
// メモリ使用量が大きい func myProtocol() -> MyProtocol { return MyClass() } // メモリ使用量が小さい func myProtocol() -> some MyProtocol { return MyClass() }
実際に比較してみた結果は下のとおりです。
1,000,000個のインスタンスを作って、メモリ使用量を比較してみました。
結果を見るとsome MyProtocol
はMyClass
とほぼパフォーマンスを出していることがわかります。
戻り値の型 | 1回目の測定結果 | 2回目の測定結果 | 3回目の測定結果 | 平均 |
---|---|---|---|---|
MyClass | 28.9mb | 27.2mb | 31.1mb | 29.1mb |
MyProtocol | 48.7mb | 58.3mb | 61.4mb | 56.2mb |
some MyProtocol | 28mb | 32.4mb | 31.6mb | 30.7mb |
100万回個のインスタンス生成の実装は以下の通りです。
let results = (0...1000000).map { _ in myProtocol() }
メモリ使用量はXcodeのMemory reportのHighの値を使用しました。
以下のように実行速度についても調べたのですが、こちらは大きな差はありませんでした。
let d = Date() let results = (0...1000000).map { _ in myProtocol() } print(Date().timeIntervalSince1970 - d.timeIntervalSince1970)
someキーワードはクラスにも使えるのですが、当然ながらsomeがあってもなくてもメモリ使用量に差は見えませんでした。
func myProtocol() -> some MyClass { return MyClass() }
余談ですがSwiftUIのViewからsomeを外すとProtocol 'View' can only be used as a generic constraint because it has Self or associated type requirements
というエラーになります。
これはViewが、associatedtypeを使っているためです。
struct ContentView: View { var body: View { Text("Hello, World!") } }
someは引数には使うことができません。
引数はジェネリクスで対処する必要があります。
func myProtocol(myClass: some MyProtocol) { } // → x func myProtocol<A: MyProtocol>(myClass: A) { } // → o