RouterパターンとSwiftDataを併用するとEXC_BAD_ACCESSが発生
以前の私の記事で、Router
パターンを使い、sheetの出しわけを行いました。
sheetの出し分け
iOS17からデータを永続化するフレームワークとしてSwiftData
が使えます。今回このSwiftData
を使っている中で、SwiftUIのRouterパターンを使っていたらEXC_BAD_ACCESS
に遭遇したので、調査と解決策を書き残します。
事象
- RouterパターンとSwiftDataを使っている場合
- データを追加・保存した後にViewを閉じようとするとCrashする
extension Identifiable where Self: Hashable {
typealias ID = Self
var id: Self { self }
}
ここのvar id: Self { self }
でEXC_BAD_ACCESS
が発生しました。
エラーログを見ても、全く原因箇所がわかりませんでした。
仮定
Routerの実装としては、以下のようになります。
enum ContentRouter: View, Hashable, Identifiable {
case first
case second(text: String)
var body: some View {
switch self {
case .first: return AnyView(FirstView())
case .second(let text): return AnyView(SecondView(text: text))
}
}
}
struct ContentView: View {
...
}
.sheet(item: $contentRouter) { $0 }
}
ここで、sheetのitemがIdentifiable
に準拠する必要があります。
sheet(item:onDismiss:content:) | Apple Developer Documentation
なので、Routerを実装する際には、Identifiable
の準拠が必要になります。
Routerはenumで実装されているので、単純な実装では実現不可能なため、以下のextensionを追加します。
extension Identifiable where Self: Hashable {
typealias ID = Self
var id: Self { self }
}
しかし、この実装を入れると、SwiftDataの保存ができなくなってしまいました。
なので、このextensionがSwiftDataに影響していると考えられます。
解決策
SwiftDataのPersistentModel
を調べると、
PersistentModel | Apple Developer Documentation
protocol PersistentModel : AnyObject, Observable, Hashable, Identifiable
とあるので、RouterのHashable, Identifiable
と同じことがわかります。つまり、
extension Identifiable where Self: Hashable
の部分がPersistentModel
に影響されていると考えられます。
なので、extension Identifiable
の部分を修正します。やりたいこととしては、Routerの時のみにIdentifiable
をextensionします。
protocol Router: Hashable {}
extension Identifiable where Self: Router {
typealias ID = Self
var id: Self { self }
}
enum ContentRouter: View, Router, Identifiable {
...
}
これでEXC_BAD_ACCESS
は発生しなくなりました。
Discussion