はじめに
あるプロトコル(ここでは View
)を返す関数を持つプロトコルを定義したいとします。
素直に書くとビルドエラーとなってしまいます。
// ❌: ビルドエラーになる
protocol SakatsuRouterProtocol {
func settingsScreen() -> some View
}
ビルドエラーは以下の通りです。
'some' type cannot be the return type of a protocol requirement; did you mean to add an associated type?
(「some」はプロトコルの戻り値の型にできません。associated typeを追加するということですか?)
このビルドエラーを解決して、プロトコルで関数の戻り値に some
を使う方法を紹介します。
環境
- OS:macOS Ventura 13.2
- Xcode:14.2 (14C18)
- Swift:5.7.2
結論
ビルドエラーに記載されている通り、 associatedtype
を使うことで解決できます。
protocol SakatsuRouterProtocol {
- func settingsScreen() -> some View
+ associatedtype SettingsScreenType: View
+ func settingsScreen() -> Self.SettingsScreenType
}
このプロトコルに準拠すると、 some View
を戻り値にできます。
extension SakatsuRouter: SakatsuRouterProtocol {
func settingsScreen() -> some View // ⭕: `some View` を戻り値にできる
}
原因
Opaque Result Type(ORT、 some
を戻り値としている型のこと)は、ビルド時に型が一意に決まる必要があるためです。
プロトコルは準拠する型によって戻り値の型も変わるため、使えないということです。
気をつけること
associatedtype
を使う際に気をつけるべきことを紹介します。
違う型になるときは同じassociatedtypeを使わない
当たり前かもしれませんが、2つ以上関数があるときに、戻り値が異なる場合は同じ associatedtype
を使ってはいけません。
// ❌: 2つ以上の関数の戻り値に同じ `associatedtype` を使っている
protocol SakatsuRouterProtocol {
associatedtype Screen: View
func settingsScreen() -> Self.Screen
func testScreen() -> Self.Screen
}
プロトコルの準拠時に、以下のビルドエラーが発生します。
Type 'SakatsuRouter' does not conform to protocol 'SakatsuRouterProtocol'
関数ごとに associatedtype
を作ると解決できます。
protocol SakatsuRouterProtocol {
- associatedtype Screen: View
+ associatedtype SettingsScreen: View
+ associatedtype TestScreen: View
- func settingsScreen() -> Self.Screen
- func testScreen() -> Self.Screen
+ func settingsScreen() -> Self.SettingsScreen
+ func testScreen() -> Self.TestScreen
}
associatedtypeの名前が重複してはいけない
associatedtype
の名前が重複している別々のプロトコルがあるとします。
// `Screen` が重複している
protocol SakatsuRouterProtocol {
associatedtype Screen: View
func settingsScreen() -> Self.Screen
}
protocol TestRouterProtocol {
associatedtype Screen: View
func testScreen() -> Self.Screen
}
両方のプロトコルを1つのクラスで準拠すると、ビルドエラーとなります。
// ❌: `associatedtype` 名が重複しているプロトコルに準拠している
extension FooRouter: SakatsuRouterProtocol {
func settingsScreen() -> some View
}
extension FooRouter: TestRouterProtocol {
func testScreen() -> some View
}
ビルドエラーの内容から原因が気づきにくいので注意です。
Type 'SakatsuRouter' does not conform to protocol 'SakatsuRouterProtocol'
Do you want to add protocol stubs?
Cannot find type 'view' in scope
associatedtype
の名前を一意にすることで解決できます。
protocol SakatsuRouterProtocol {
- associatedtype Screen: View
- func settingsScreen() -> Self.Screen
+ associatedtype SettingsScreen: View
+ func settingsScreen() -> Self.SettingsScreen
}
protocol TestRouterProtocol {
- associatedtype Screen: View
- func testScreen() -> Self.Screen
+ associatedtype TestScreen: View
+ func testScreen() -> Self.TestScreen
}
associatedtypeと他の型の名前が重複してはいけない
先ほどとほぼ同じですが、 associatedtype
と他の型で名前が重複してもビルドエラーが発生します。
// ❌: `SettingsScreen` という名前が重複している
protocol SakatsuRouterProtocol {
associatedtype SettingsScreen: View
func settingsScreen() -> Self.SettingsScreen
}
struct SettingsScreen: View {
// ...
}
ビルドエラーの内容は先ほどと同様なので省略します。
私は associatedtype
の末尾に Type
を付けることで重複を回避しています。
protocol SakatsuRouterProtocol {
- associatedtype SettingsScreen: View
- func settingsScreen() -> Self.SettingsScreen
+ associatedtype SettingsScreenType: View
+ func settingsScreen() -> Self.SettingsScreenType
}
struct SettingsScreen: View {
// ...
}
おわりに
これでプロトコルの関数の戻り値に some
が使えるようになりました
この手法はSwiftUIの View
プロトコルの body
の戻り値で実際に使われており、それを参考にしました。