🎉

RouterパターンとSwiftDataを併用するとEXC_BAD_ACCESSが発生

2024/01/11に公開

以前の私の記事で、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