SwiftData Fatal error: failed to find a currently active container 📦

SwiftData Fatal error: failed to find a currently active container

まったく動かない。画面が真っ白。再起動でも同じ。

エラーメッセージ。

SwiftData/ModelContainer.swift:159: Fatal error: failed to find a currently active container for Task
Failed to find any currently loaded container for Task)

コンテナを渡す前に初期化すると問題が解決されるようです。


@main
struct MyApp: App {
  let modelContainer: ModelContainer
    
  init() {
    do {
      modelContainer = try ModelContainer(for: Item.self)
    } catch {
      fatalError("Could not initialize ModelContainer")
    }
  }
    
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
    .modelContainer(modelContainer)
  }
}

👉 SwiftData Fatal error: failed to find a currently active container | Apple Developer Forums hatena-bookmark

というかんじで、モデルコンテナの初期化位置を最上位にすると直りました。

モデルデータの変更後の整合性の問題でしょうか。

突然動かなくなるのでびっくりします。

 

📦 どこでモデルコンテナを生成するべきか

利用する View につければ、


struct ContentView: View {
  @State private var container = ModelContainer(...)

  var body: some Scene {
    RecipesList()
      .modelContainer(container)
  }
}

それ以下では @Environmentを使って問題なく利用できると思っていましたが。


struct RecipesList: View {
    @Environment(\.modelContext) private var modelContext

The environment’s modelContext property will be assigned a new context associated with this container. All implicit model context operations in this view, such as Query properties, will use the environment’s context.

Environment ののmodelContext プロパティには、このコンテナに関連付けられた新しいコンテキストが割り当てられます。Query プロパティなど、このビューのすべての暗黙のモデルコンテキスト操作は、Environment のコンテキストを使用します。

👉 modelContainer(_:) | Apple Developer Documentation hatena-bookmark

Modifier や Preview などあちこちで 生成していると、利用できる範囲が分かりづらくなるのは確か。



Swift で Singleton

前回、Apple サンプルコードを見ながら、生存期間が謎だったのですが。

👉 【Swift】この ModelActor ってなぜ生きてるの? hatena-bookmark

シンプルなクラスにするとこういうことですね。


class Cat {
  private(set) static var shared: Cat!

  private init() {}

  static func createInstance() {
    shared = Cat()
  }

  func show() {
    print("ねこ")
  }
}

Cat.createInstance()
Cat.shared.show() // ねこ

一度インスタンスを生成すると、

全スコープから参照可能な状態で、アプリが死ぬまで生きてるんですね。


👉 Singleton pattern - Wikipedia hatena-bookmark

どう書いてますか Singleton。

少し Xcode PlayGround で試してみます。

ざっくりこのようなイメージでいたけども。


class Monkey {
  private(set) static var instance: Monkey?

  private init() {}

  static func createInstance() -> Monkey {
    if (instance == nil) {
      instance = Monkey()
    }
    return instance!
  }

  func show() {
    print("さる")
  }
}

var monkey: Monkey? = Monkey.createInstance()
monkey?.show() // "さる"
monkey = nil
monkey?.show() // not show

公式ドキュメントを見ると以下のような感じになってます。

👉 Managing a Shared Resource Using a Singleton | Apple Developer Documentation hatena-bookmark


class Dog {
  static let shared = Dog()

  func show() {
    print("いぬ")
  }
}

Dog.shared.show() // いぬ


class Kiji {
  static let shared: Kiji = {
    let instance = Kiji()
    return instance
  }()

  func show() {
    print("きじ")
  }
}

Kiji.shared.show() // きじ

var shared: Kiji? = Kiji.shared
shared?.show() // きじ
shared = nil
shared?.show() // not show

本当に死んでるのか?

Swift では、結構嫌がられてますよね Singleton。

使いすぎて収集つかずに太っちゃうやつですかね。

👉 Automatic Reference Counting | Documentation hatena-bookmark
👉 Kotlin で書きたい「正しいシングルトン(Singleton)」 hatena-bookmark


【Swift】この ModelActor ってなぜ生きてるの?

公式サンプルコードを見ながら作りました。



👉 【SwiftUI】SwiftData でスレッドセーフにバックグラウンドでデータを扱う - @ModelActor hatena-bookmark

🔄 謎

サンプルコードに従った流れで実装しました。

ModelActor の内部 に static な自己インスタンスを持って、


@ModelActor
actor BirdBrain {
    private(set) static var shared: BirdBrain!
    
    static func createSharedInstance(modelContext: ModelContext) {
        shared = BirdBrain(modelContainer: modelContext.container)
    }
    
    func process(transaction verificationResult: VerificationResult<Transaction>) async {
    }
    
    func status(for statuses: [Product.SubscriptionInfo.Status], ids: PassIdentifiers) -> PassStatus {
    }
    
    func checkForUnfinishedTransactions() async {
    }
    
    func observeTransactionUpdates() {
    }

View の onAppear() でインスタンス生成。ModelActor 内の関数は Task 内で呼ぶ。


struct BackyardBirdsShopViewModifier: ViewModifier {
    @Environment(\.modelContext) private var modelContext
    
    func body(content: Content) -> some View {
        ZStack {
            content
        }
        .subscriptionPassStatusTask()
        .onAppear {
            BirdBrain.createSharedInstance(modelContext: modelContext)
        }
        .task {
            await BirdBrain.shared.observeTransactionUpdates()
            await BirdBrain.shared.checkForUnfinishedTransactions()
        }

👉 sample-backyard-birds/Multiplatform/Shop/BirdBrain.swift at 832515e4abb9224c1970e40a3bd9b82900019187 · apple/sample-backyard-birds hatena-bookmark
👉 sample-backyard-birds/Multiplatform/Shop/BackyardBirdsShopViewModifier.swift at main · apple/sample-backyard-birds hatena-bookmark

というかんじで、

  • ModelActor の内部 に static な自己インスタンスを持つ。
  • View の onAppear() でインスタンス生成。
  • ModelActor 内の関数は Task 内で呼ぶ。

このきまりで実装すると動くっちゃあ動くけど。

しかし、この ModelActor ってなぜ生きてるの?

いつ死ぬの?

死んでるように見えるけど、なぜ関数が呼べるのですか。

View と一緒に死ぬ?

(→ つづく)