LoginSignup
17
9

More than 1 year has passed since last update.

プロトコルで関数の戻り値にsomeを使う方法(Swift)

Last updated at Posted at 2023-02-11

はじめに

あるプロトコル(ここでは View )を返す関数を持つプロトコルを定義したいとします。

素直に書くとビルドエラーとなってしまいます。

SakatsuRouterProtocol.swift
// ❌: ビルドエラーになる
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 を使うことで解決できます。

SakatsuRouterProtocol.swift
protocol SakatsuRouterProtocol {
-   func settingsScreen() -> some View
+   associatedtype SettingsScreenType: View
+   func settingsScreen() -> Self.SettingsScreenType
}

このプロトコルに準拠すると、 some View を戻り値にできます。

SakatsuRouter.swift
extension SakatsuRouter: SakatsuRouterProtocol {
  func settingsScreen() -> some View // ⭕: `some View` を戻り値にできる
}

原因

Opaque Result Type(ORT、 some を戻り値としている型のこと)は、ビルド時に型が一意に決まる必要があるためです。
プロトコルは準拠する型によって戻り値の型も変わるため、使えないということです。

気をつけること

associatedtype を使う際に気をつけるべきことを紹介します。

違う型になるときは同じassociatedtypeを使わない

当たり前かもしれませんが、2つ以上関数があるときに、戻り値が異なる場合は同じ associatedtype を使ってはいけません。

SakatsuRouterProtocol.swift
// ❌: 2つ以上の関数の戻り値に同じ `associatedtype` を使っている
protocol SakatsuRouterProtocol {
  associatedtype Screen: View
  func settingsScreen() -> Self.Screen
  func testScreen() -> Self.Screen
}

プロトコルの準拠時に、以下のビルドエラーが発生します。

Type 'SakatsuRouter' does not conform to protocol 'SakatsuRouterProtocol'

関数ごとに associatedtype を作ると解決できます。

SakatsuRouterProtocol.swift
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つのクラスで準拠すると、ビルドエラーとなります。

FooRouter.swift
// ❌: `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 が使えるようになりました :relaxed:

この手法はSwiftUIの View プロトコルの body の戻り値で実際に使われており、それを参考にしました。

参考リンク

17
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
9