はじめに
SwiftUIにおいて、インスタンスを伝播させるためには environment()
モディファイアを使います。
ただ受け取り側がその具象型を知らない場合、そのままでは受け取れません。
本記事では具象型を知らなくても受け取れるよう、プロトコルを介してインスタンスを注入する方法を紹介します。
環境
- OS: macOS Sonoma 14.5
- Xcode: 16.2 (16C5032a)
- Swift: 6.0.3
注意
iOS 17.0+からはObservationフレームワークを使えるので、Combineフレームワークを使うときと書き方が異なります。
本記事ではiOS 16以前をサポートしているプロジェクトでも使えるよう、両方の書き方を併記します。
environment()
でプロトコルを介してインスタンスを注入する
プロトコルの定義
注入の受け口となるプロトコルを定義します。
import SwiftUI
// Use Observation (iOS 17.0+)
@MainActor
protocol Foo: AnyObject, Observable, Sendable {
func bar()
}
// Use Combine (iOS 13.0+)
@MainActor
protocol Foo: ObservableObject, Sendable {
func bar()
}
公式ドキュメントに記載されている通り、Observationを使うときは AnyObject
と Observable
、Combineを使うときは ObservableObject
に準拠する必要があります。
Sendable
への準拠は必須ではありませんが、準拠する場合が多いと思うので記述しています。
プロトコルに準拠したクラスの定義
先ほど作成したプロトコルに準拠したクラスを定義します。
// Use Observation (iOS 17.0+)
@Observable
final class DefaultFoo {}
extension DefaultFoo: Foo {
bar() {
print("Bar")
}
}
// Use Combine (iOS 13.0+)
final class DefaultFoo {}
extension DefaultFoo: Foo {
bar() {
print("Bar")
}
}
Observationを使うときは @Observable
マクロを付ける必要があります。違いはこれだけです。
インスタンスの生成と注入
先ほど作成したクラスのインスタンスを生成して注入します。
import SwiftUI
struct RootScreen: View {
private let foo = DefaultFoo()
var body: some View {
FooScreen<DefaultFoo>()
.environment(foo) // Use Observation (iOS 17.0+)
.environmentObject(foo) // Use Combine (iOS 13.0+)
}
}
}
Observationでは environment()
、Combineでは environmentObject()
を使ってインスタンスを注入します。
型パラメータで具象型を渡す必要があるのに注意です。
インスタンスの受け取り
注入されたインスタンスを受け取ります。
import SwiftUI
struct FooScreen<F: Foo>: View {
@Environment(F.self) private var foo // Use Observation (iOS 17.0+)
@EnvironmentObject private var foo: F // Use Combine (iOS 13.0+)
var body: some View {
Text("Foo")
.onAppear {
foo.bar()
}
}
Observationでは @Environment
、Combineでは @EnvironmentObject
を使ってインスタンスを取得します。
おわりに
これで environment()
でプロトコルを介してインスタンスを注入できました。
依存性逆転の原則(DIP)の実現に有用なので、みなさんも使ってみてください
参考リンク
- Xユーザーのウホーイさん: 「iOS 17.0+ で Environment を protocol で受け取るにはどうしたらいいんだろ? https://t.co/0bwryW5VgJ」 / X
- environment(_:) | Apple Developer Documentation
- environmentObject(_:) | Apple Developer Documentation
- Observation | Apple Developer Documentation
- Migrating from the Observable Object protocol to the Observable macro | Apple Developer Documentation
- Show session by uhooi · Pull Request #3 · uhooi/VimConf-2024-iOS
- Migrating
Router
protocol from theObservableObject
to the@Observable
by treastrain · Pull Request #4 · uhooi/VimConf-2024-iOS