SwiftData でのバックグラウンドでの大量データインサート

著名なサイトのこのコード。

```swift
actor BackgroundImporter {
    var modelContainer: ModelContainer

    init(modelContainer: ModelContainer) {
        self.modelContainer = modelContainer
    }

    func backgroundInsert() async throws {
        let modelContext = ModelContext(modelContainer)

        let batchSize = 1000
        let totalObjects = 100_000

        for i in 0..<(totalObjects / batchSize) {
            for j in 0..<batchSize {
                // try await Task.sleep(for: .milliseconds(1))
                let issue = Movie(title: "Movie \(I * batchSize + j)", cast: [])
                modelContext.insert(issue)
            }

            try modelContext.save()
        }
    }
}
```

https://www.hackingwithswift.com/quick-start/swiftdata/how-to-batch-insert-large-amounts-of-data-efficiently

ここの部分。

```swift
// try await Task.sleep(for: .milliseconds(1))
```

説明としては以下のように書いています。

>It has a commented-out 1-millisecond pause between each object insertion – the code goes as fast as possible, but if you want to throttle your inserts you should uncomment that line.

> 各オブジェクトの挿入の間にコメントアウトされた 1 ミリ秒の一時停止があります。コードは可能な限り高速に実行されますが、挿入を抑制したい場合は、その行のコメントを解除する必要があります。

怪しいですよね。

試してみます。

# 🤔 やってみたが

似たコードがあったのでデータ10万行で試してみました。

少し思ったことをいくつか。

### 🧑🏻‍💻 なぜこのコメントアウト行を書いたのか

自然に考えると不要ですよね。

「挿入を抑制したい場合」の意味については、

著者は書いてません。

が、コメントアウトのままでは、

途中で画面に反映されなかった。

実行環境に依る話しなのかもしれません。

表面的にデリケートな奥深い話しなのでしょうか。


### 🧑🏻‍💻 sleep のタイミング

数十万レコードを処理してみたのですが、遅すぎる。

どっちみち、メインスレッドに反映のタイミングは、

```swift
try modelContext.save()
```

なので、

```Swift
for i in 0..<(totalObjects / batchSize) {
    for j in 0..<batchSize {
        let issue = Movie(title: "Movie \(I * batchSize + j)", cast: [])
        modelContext.insert(issue)
    }
    
    try modelContext.save()
    try await Task.sleep(for: .milliseconds(1)) // *
}
```
のが実行時間的には早いし、

```save()``` のタイミングで画面反映もされる。

まさか、insert の順序が交差してしまうのか ?



### 🧑🏻‍💻 ```@ModelActor``` を使う

こここそ使うところなんじゃね?



```swift
@ModelActor
actor BackgroundImporter {
    func backgroundInsert() async throws {
        let batchSize = 1000
        let totalObjects = 100_000

        for i in 0..<(totalObjects / batchSize) {
            for j in 0..<batchSize {
                let issue = Movie(title: "Movie \(I * batchSize + j)", cast: [])
                modelContext.insert(issue)
            }

            try modelContext.save()
            try await Task.sleep(for: .milliseconds(1))
        }
    }
}
```

呼び出し側。

```Swift
@Environment(\.modelContext) private var modelContext

private var importer: BackgroundImporter {
    BackgroundImporter(modelContainer: modelContext.container)
}
```

actor 側で、いきなり、```modelContext``` を使ってますが、

イニシャライザーなどマクロが書き出してますので

Xcode から expand で見ることができます。


https://android.benigumo.com/20231211/modelactor/

# 🤔 まとめ

などやっていると、怖くなってきます SwiftData。

https://x.com/maochanz/status/1808084351974101019


<blockquote class="twitter-tweet"><p lang="et" dir="ltr">【SwiftUI + SwiftData】ScrollView + LazyVStack vs Large amounts of data <a href="https://t.co/I79u1tkDkw">https://t.co/I79u1tkDkw</a> <a href="https://twitter.com/hashtag/Swift?src=hash&amp;ref_src=twsrc%5Etfw">#Swift</a> <a href="https://twitter.com/hashtag/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E5%AD%A6%E7%BF%92?src=hash&amp;ref_src=twsrc%5Etfw">#プログラミング学習</a></p>&mdash; chanzmao (@maochanz) <a href="https://twitter.com/maochanz/status/1806282787319882171?ref_src=twsrc%5Etfw">June 27, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Emerge Tools ブログ | SwiftData と Realm: パフォーマンス比較 <a href="https://t.co/M1smRe0GNX">https://t.co/M1smRe0GNX</a></p>&mdash; chanzmao (@maochanz) <a href="https://twitter.com/maochanz/status/1805597205459616256?ref_src=twsrc%5Etfw">June 25, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>