UI スレッドをブロックしてるんですよね。
10万レコードのような大きいデータを fetch()
すると。
バックグラウンドで大量インサートなどしながら、
UI側でリストなどをスクロールするともっと顕著に分かります。
どうやら、
「インスタンス生成をどこで行うか」
が大事なようです。
singleton の ModelActor
側。
@ModelActor
actor DataSource {
nonisolated(unsafe) private(set) static var shared: DataSource!
// static func createInstance(modelContainer: ModelContainer) {
// print("createInstance()", Thread.current) // main
// shared = DataSource(modelContainer: modelContainer)
// }
static func createInstance(modelContainer: ModelContainer) async {
print("createInstance()", Thread.current) // not main
shared = DataSource(modelContainer: modelContainer)
}
View
側。インスタンス生成はバックグラウンドで。
// .onAppear { // main
// DataSource.createInstance(modelContainer: modelContext.container)
// }
.task { // not main
await DataSource.createInstance(modelContainer: modelContext.container)
}
どうなんすかね。
import SwiftUI | |
import SwiftData | |
// https://forums.developer.apple.com/forums/thread/736226 | |
struct TestModelActorView: View { | |
@Environment(\.modelContext) private var modelContext | |
// private var dataSource: DataSource { | |
// // ModelActor | |
// DataSource(modelContainer: modelContext.container) | |
// } | |
@State private var count = 0 | |
@State private var task: Task<Void, Error>? | |
var body: some View { | |
VStack { | |
Text("\(count)") | |
Button("fetch") { | |
task = Task { | |
//let items = await dataSource.fetch() | |
//let items = await DataSource(modelContainer: modelContext.container).fetch() | |
let items = await DataSource.shared.fetch() | |
count = items.count | |
} | |
} | |
Button("cancel") { | |
task?.cancel() | |
} | |
} | |
.buttonStyle(.borderedProminent) | |
// .onAppear { // main | |
// DataSource.createInstance(modelContainer: modelContext.container) | |
// } | |
.task { // not main | |
await DataSource.createInstance(modelContainer: modelContext.container) | |
} | |
} | |
} | |
@ModelActor | |
actor DataSource { | |
nonisolated(unsafe) private(set) static var shared: DataSource! | |
// | |
// static func createInstance(modelContainer: ModelContainer) { | |
// print("createInstance()", Thread.current) // main | |
// shared = DataSource(modelContainer: modelContainer) | |
// } | |
// | |
static func createInstance(modelContainer: ModelContainer) async { | |
print("createInstance()", Thread.current) // not main | |
shared = DataSource(modelContainer: modelContainer) | |
} | |
func fetch() async -> [Item] { | |
//print("fetch()", Thread.current) // depends on creating instance | |
do { | |
let items = try modelContext.fetch(FetchDescriptor<Item>()) | |
//let items: [Item] = [] | |
//try await Task.sleep(for: .seconds(3)) | |
return items | |
} catch { | |
if Task.isCancelled { | |
print("canceled") | |
} | |
return [] | |
} | |
} | |
} | |
extension Item: @unchecked Sendable {} | |
#Preview { | |
TestModelActorView() | |
.dataContainer() | |
.padding() | |
} |
SwiftData も苦しんでる感じに見えます。