Share
Embed
(iOSDC Japan 2024) Concurrency Safe SwiftData - 並行安全なSwiftDataの使い方
Himeshi
August 24, 2024
Transcript
68 slides
-
4XJGU%BUBͷڍಈྫ 7 ࣍ͷૢ࡞Λճߦ͏ɻ w 'FUDI w Λߋ৽ ʢTDPSFʹΛՃࢉʣ let result:
ExamResult = try context.fetch(…).first! result.score += 10 try context.save() -
ฒྻ࣮ߦ 10 "DUPS "DUPS 'FUDI 4DPSF 4DPSF 'FUDI
4DPSF 4DPSF ࣌ؒ -
4DPSFʹͳΔ 12 "DUPS "DUPS 'FUDI 4DPSF 4DPSF 'FUDI
4DPSF 4DPSF 4DPSF 4DPSF ࣌ؒ -
λΠϛϯά͕ζϨΔͱʁ 15 "DUPS "DUPS 'FUDI 4DPSF 4DPSF 'FUDI
4DPSF 4DPSF 4DPSF 4DPSF ࣮ߦຖʹҟͳΔڍಈΛ͢Δ ࣌ؒ -
4XJGU%BUBͷߏཁૉ 22 .PEFM$POUBJOFS .PEFM$POUFYU 1FSTJTUFOU.PEFM 'FUDI *OTFSU @Model class ExamResult
{ var studentID: Int var score: Int } -
1FSTJTUFOU.PEFMΛฦ͢ 25 func fetchFirstResult() async throws -> ExamResult? { let
context = ModelContext(modelContainer) return try context.fetch(FetchDescriptor<ExamResult>()) .first } @MainActor func updateLabel() async { let firstResult = try! await fetchFirstResult()! label.text = "Score: \(firstResult.score)" } -
1FSTJTUFOU.PEFMΛฦ͢ 26 func fetchFirstResult() async throws -> ExamResult? { let
context = ModelContext(modelContainer) return try context.fetch(FetchDescriptor<ExamResult>()) .first } @MainActor func updateLabel() async { let firstResult = try! await fetchFirstResult()! label.text = "Score: \(firstResult.score)" } -
1FSTJTUFOU.PEFMΛฦ͢ 27 func fetchFirstResult() async throws -> ExamResult? { let
context = ModelContext(modelContainer) return try context.fetch(FetchDescriptor<ExamResult>()) .first } @MainActor func updateLabel() async { let firstResult = try! await fetchFirstResult()! label.text = "Score: \(firstResult.score)" } Non-sendable type 'ExamResult?' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary -
ʮʯΛऔಘ͍ͨ͠ͱ͖ 28 func fetchFirstResult() async throws -> ExamResult? { let
context = ModelContext(modelContainer) return try context.fetch(FetchDescriptor<ExamResult>()) .first } @MainActor func updateLabel() async { let firstResult = try! await fetchFirstResult()! label.text = "Score: \(firstResult.score)" } -
func fetchFirstResult() async throws -> ExamResult? { let context =
ModelContext(modelContainer) return try context .fetch(FetchDescriptor<ExamResult>()) .first } ʮʯΛऔಘ͍ͨ͠ͱ͖ 29 /PO4FOEBCMF -
4FOEBCMFͳͰฦ͢ 30 func fetchFirstScore() async throws -> Int? { let
context = ModelContext(modelContainer) return try context .fetch(FetchDescriptor<ExamResult>()) .first?.score } औಘ͞ΕΔ͕΄͍͠ ˠ4FOEBCMFͳͰฦ͢ 4FOEBCMF" -
ࢀর͕ඞཁͳ߹ 31 @MainActor class ExamResultCell { var result: ExamResult? func
fetch() async throws { result = try await fetchFirstResult() } func incrementScore() { result?.score += 10 } } -
ࢀর͕ඞཁͳ߹ 32 @MainActor class ExamResultCell { var result: ExamResult? func
fetch() async throws { result = try await fetchFirstResult() } func incrementScore() { result?.score += 10 } } -
ࢀর͕ඞཁͳ߹ 33 @MainActor class ExamResultCell { var result: ExamResult? func
fetch() async throws { result = try await fetchFirstResult() } func incrementScore() { result?.score += 10 } } Non-sendable type 'ExamResult?' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary -
func fetchFirstResult() async throws -> ExamResult? { let context =
ModelContext(modelContainer) return try context .fetch(FetchDescriptor<ExamResult>()) .first } ࢀর͕ඞཁͳ߹ 34 /PO4FOEBCMF -
func fetchFirstResultID() async throws -> PersistentIdentifier? { let context =
ModelContext(modelContainer) return try context .fetchIdentifiers(FetchDescriptor<ExamResult>()) .first } 1FSTJTUFOU*EFOUJGJFSΛ͏ 35 4FOEBCMF" -
1FSTJTUFOU*EFOUJGJFSΛ͏ 36 @MainActor class MyListCell { var resultID: PersistentIdentifier? func
fetch() async throws { resultID = try await fetchFirstResultID() } func incrementScore() throws { let result = try modelContainer.mainContext .fetch(FetchDescriptor<ExamResult>( predicate: #Predicate { $0.persistentModelID == resultID })).first result?.score += 10 } } -
1FSTJTUFOU*EFOUJGJFSΛ͏ 37 @MainActor class MyListCell { var resultID: PersistentIdentifier? func
fetch() async throws { resultID = try await fetchFirstResultID() } func incrementScore() throws { let result = try modelContainer.mainContext .fetch(FetchDescriptor<ExamResult>( predicate: #Predicate { $0.persistentModelID == resultID })).first result?.score += 10 } } -
ෆ࣮֬ͳ݁ՌΛͨΒ͢ྫ 42 "DUPS "DUPS 'FUDI 4DPSF 4DPSF 'FUDI
4DPSF 4DPSF 4DPSF 4DPSF ࣌ؒ -
ෆ࣮֬ͳ݁ՌΛͨΒ͢ྫ 43 "DUPS "DUPS 'FUDI 4DPSF 4DPSF 'FUDI
4DPSF 4DPSF 4DPSF 4DPSF ࣌ؒ -
͜ͷίʔυฒߦ҆શͰͳ͍ 44 Task { let context = ModelContext(modelContainer) let result
= try context.fetch(…).first! result.score += 10 try context.save() } Task { let context = ModelContext(modelContainer) let result = try context.fetch(…).first! result.score += 10 try context.save() } ˞4XJGUϞʔυͰίϯύΠϧՄೳ -
0WFSXSJUF 47 @Model class FooItem { var a = 1
var b = 10 var c = 100 } -
0WFSXSJUF 48 @Model class FooItem { var a = 1
var b = 10 var c = 100 } item.a = 2 item.b = 20 item.a = 3 item.c = 300 ࣌ؒ @Model class FooItem { var a = ??? var b = ??? var c = ??? } -
0WFSXSJUF 49 @Model class FooItem { var a = 1
var b = 10 var c = 100 } item.a = 2 item.b = 20 item.a = 3 item.c = 300 ࣌ؒ -
0WFSXSJUF 50 @Model class FooItem { var a = 1
var b = 10 var c = 100 } item.a = 2 item.b = 20 item.a = 3 item.c = 300 ࣌ؒ @Model class FooItem { var a = ??? var b = ??? var c = ??? } -
શͯͷϓϩύςΟ্͕ॻ͖͞ΕΔ 51 @Model class FooItem { var a = 1
var b = 10 var c = 100 } item.a = 2 item.b = 20 item.a = 3 item.c = 300 ࣌ؒ @Model class FooItem { var a = 3 var b = 10 var c = 300 } item.b = 10 -
'V[[Z3FBEͷྫ 53 "DUPS "DUPS 'FUDI 4DPSF 4DPSF 'FUDI
4DPSF 4DPSF 4DPSF ࣌ؒ -
'V[[Z3FBEͷྫ 54 "DUPS "DUPS 'FUDI 4DPSF 4DPSF 'FUDI
4DPSF 4DPSF 4DPSF ࣌ؒ -
͜ΕΒͷݱΛ͍͗ͨ 56 w 0WFSXSJUF w ॻ͖ࠐΈ݁Ռ͕ผͷॻ͖ࠐΈʹΑͬͯॻ͖͑ΒΕΔ w 'V[[Z3FBE w ಡΈ͜ΉλΠϛϯάʹΑ͕ͬͯҟͳΔ
ͭͷBDUPSDPOUFYUͰྻ࣮ߦ͢ΕΑ͍ -
ू͢Δ"DUPSͷൺֱ 60 .BJO"DUPS (MPCBM"DUPS ϝϦοτ w 6*ʹಉظతʹ ใΛͤΔ w ࣮͕
ͱͯ؆୯ 6*εϨουΛ ϒϩοΫ͠ͳ͍ σϝϦοτ 6*εϨουΛ ϒϩοΫ͢Δ 6*ͱͷΓͱΓ͕ ඇಉظʹͳΔ -
ू͢Δ"DUPSͷൺֱ 61 .BJO"DUPS (MPCBM"DUPS ϝϦοτ w 6*ʹಉظతʹ ใΛͤΔ w ࣮͕
ͱͯ؆୯ 6*εϨουΛ ϒϩοΫ͠ͳ͍ σϝϦοτ 6*εϨουΛ ϒϩοΫ͢Δ 6*ͱͷΓͱΓ͕ ඇಉظʹͳΔ Φεεϝ -
NBJO$POUFYUͷซ༻ 63 struct ExamResultList: View { @Query private var results:
[ExamResult] var body: some View { List(results) { result in Text("score: \(result.score)") } } } let list = ExamResultList() .modelContext(ModelContext(container)) @Query ϚΫϩNBJO$POUFYUͷར༻Λڧ੍͢Δɻ ඞͣ.BJO"DUPSJTPMBUFE -
NBJO$POUFYUͷซ༻ 64 w .BJO"DUPSͰͷऔಘ͚ͩߦ͏ w 0WFSXSJUFΛ͙ ࣌ؒ (MPCBM"DUPS .BJO"DUPS ॻ͖ࠐΈ
ಡΈࠐΈ ॻ͖ࠐΈ ಡΈࠐΈ ಡΈࠐΈ 0WFSXSJUF -
NBJO$POUFYUͷซ༻ 65 w Ͱ͖Δ͚ͩҰʹGFUDI͢Δ w 'V[[ZSFBEΛ͙ ࣌ؒ (MPCBM"DUPS .BJO"DUPS ಡΈࠐΈ
ಡΈࠐΈ ಡΈࠐΈ ॻ͖ࠐΈ ಡΈࠐΈ 'V[[Z3FBE -
3%#ͰΑ͘ΘΕΔΞΠσΞ 71 w .PEFMΦϒδΣΫτ͝ͱʹ"DUPSΛ͚Δ @Model class ModelA { var a:
Int } @Model class ModelB { var b: String } "DUPS" "DUPS# -
3%#ͰΑ͘ΘΕΔΞΠσΞ 72 w .PEFMΦϒδΣΫτ͝ͱʹ"DUPSΛ͚Δ w ϩοΫͷ࣮ˠ#σουϩοΫͷةݥੑ# @Model class ModelA {
var a: Int } @Model class ModelB { var b: String } "DUPS" "DUPS# 5SBOTBDUJPO 5SBOTBDUJPO # # -
୯Ұॻࠐ ಉ࣌ಡࠐ 74 ಡΈࠐΈ ॻ͖ࠐΈ ॻ͖ࠐΈ ॻ͖ࠐΈ ಡΈࠐΈ ಡΈࠐΈ ಡΈࠐΈ
ಡΈࠐΈ ಡΈࠐΈ ಡΈࠐΈ ಡΈࠐΈ ࣌ؒ ಉ࣌ʹ͚ͭͩ ෳಉ࣮࣌ߦՄ -
୯Ұॻࠐ ಉ࣌ಡࠐ 75 w ॻ͖ࠐΈɺಉ࣌ʹ͚ͭͩ w 0WFSXSJUFΛ͙ w ಡΈࠐΈதɺॻ͖ࠐΈېࢭ w
'V[[ZSFBEΛ͙ w ಡΈࠐΈੑೳΛ্ͤ͞ΒΕΔ -
4DIFEVMFSͷ࣮ྫ 76 /// A primitive scheduler implementation of concurrent read
and sequential write policy actor AccessScheduler { private enum OperationType { case read, write } private struct Operation { let id: UUID let continuation: CheckedContinuation<Void, Never> let type: OperationType } let container: ModelContainer private var executingOperations: [Operation] = [] private var pendingOperations: [Operation] = [] // TODO: use queue init(container: ModelContainer) { self.container = container } /// Executes the given block sequentially as a writing transaction. /// - Parameter block: the writing transaction /// - Returns: the value which the given transaction block returns @discardableResult func executeWrite<T>(block: @Sendable (ModelContext) throws -> T) async rethrows -> T where T: Sendable { try await _execute(type: .write) { try block(ModelContext(self.container)) } } /// Executes the given block concurrently as a reading transaction. /// > Warning: Do not perform any operations which modify the persisted data. /// - Parameter block: the reading transaction /// - Returns: the value which the given transaction block returns func executeRead<T>(block: @Sendable @escaping (ModelContext) -> T) async -> T where T: Sendable { await _execute(type: .read) { await Task.detached { block(ModelContext(self.container)) }.value } } /// Executes the given block concurrently as a reading transaction. /// > Warning: Do not perform any operations which modify the persisted data. /// - Parameter block: the reading transaction /// - Returns: the value which the given transaction block returns func executeRead<T>(block: @Sendable @escaping (ModelContext) throws-> T) async throws -> T where T: Sendable { try await _execute(type: .read) { try await Task.detached { try block(ModelContext(self.container)) }.value } } private func _execute<T>( type: OperationType, block: () async throws-> T ) async rethrows -> T where T: Sendable { let id = UUID() await withCheckedContinuation { pendingOperations.append(Operation(id: id, continuation: $0, type: type)) popPendingOperations() } let result = try await block() executingOperations.removeAll { $0.id == id } popPendingOperations() return result } private func popPendingOperations() { guard executingOperations.isEmpty else { return } while !pendingOperations.isEmpty { let pendingOperation = pendingOperations.removeFirst() executingOperations.append(pendingOperation) pendingOperation.continuation.resume() guard pendingOperation.type == .read else { // Allow only one execution break } } } } IUUQTHJTUHJUIVCDPNUFBNIJNFI EFGGCDCFCD -
·ͱΊ 79 4XJGU%BUBͰ࣮ߦ݁ՌΛ҆ఆͤ͞ΔͨΊͷํ๏Λհɻ w 4USJDUDPODVSSFODZDIFDLJOHରԠ w 4FOEBCMFͳɺ1FSTJTUFOU*EFOUJpFS w ฒྻ࣮ߦʹෆ࣮֬͞ΛͨΒ͢ݱ w
0WFSXSJUFɺ'V[[ZSFBE w ҆ఆ࣮ͨ͠ߦ݁ՌΛಘΔํ๏ w ྻ࣮ߦɺಡऔฒྻ࣮ߦ -
ͻΊ͠ʢ!IJNFTIJ@UFDIʣ 80 w J04ΞϓϦΤϯδχΞ w झຯϓϩδΣΫτʰ4JNVUSBOT0531ʱ w ަ௨γϛϡϨʔγϣϯήʔϜ ʮ4JNVUSBOTʯͷຊ͚GPSL w
"JS%SPQͰମݧηοτதʂ IUUQTXXXIJNFTIJDPN