Upgrade to Pro — share decks privately, control downloads, hide ads and more …

(iOSDC Japan 2024) Concurrency Safe SwiftData -...

Himeshi
August 24, 2024

(iOSDC Japan 2024) Concurrency Safe SwiftData - 並行安全なSwiftDataの使い方

Himeshi

August 24, 2024
Tweet

More Decks by Himeshi

Other Decks in Technology

Transcript

  1. 4XJGU%BUBͷڍಈྫ 7 ࣍ͷૢ࡞Λճߦ͏ɻ w 'FUDI w ஋Λߋ৽ ʢTDPSFʹΛՃࢉʣ let result:

    ExamResult = try context.fetch(…).first! result.score += 10 try context.save()
  2. 4DPSF͸ʹͳΔ 12 "DUPS "DUPS 'FUDI 4DPSF  4DPSF  'FUDI

    4DPSF 4DPSF 4DPSF 4DPSF ࣌ؒ
  3. λΠϛϯά͕ζϨΔͱʁ 15 "DUPS "DUPS 'FUDI 4DPSF  4DPSF  'FUDI

    4DPSF 4DPSF 4DPSF 4DPSF ࣮ߦຖʹҟͳΔڍಈΛ͢Δ ࣌ؒ
  4. 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)" }
  5. 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)" }
  6. 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
  7. ʮ஋ʯΛऔಘ͍ͨ͠ͱ͖ 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)" }
  8. func fetchFirstResult() async throws -> ExamResult? { let context =

    ModelContext(modelContainer) return try context .fetch(FetchDescriptor<ExamResult>()) .first } ʮ஋ʯΛऔಘ͍ͨ͠ͱ͖ 29 /PO4FOEBCMF
  9. 4FOEBCMFͳ஋Ͱฦ͢ 30 func fetchFirstScore() async throws -> Int? { let

    context = ModelContext(modelContainer) return try context .fetch(FetchDescriptor<ExamResult>()) .first?.score } औಘ͞ΕΔ஋͕΄͍͠ ˠ4FOEBCMFͳ஋Ͱฦ͢ 4FOEBCMF"
  10. ࢀর͕ඞཁͳ৔߹ 31 @MainActor class ExamResultCell { var result: ExamResult? func

    fetch() async throws { result = try await fetchFirstResult() } func incrementScore() { result?.score += 10 } }
  11. ࢀর͕ඞཁͳ৔߹ 32 @MainActor class ExamResultCell { var result: ExamResult? func

    fetch() async throws { result = try await fetchFirstResult() } func incrementScore() { result?.score += 10 } }
  12. ࢀর͕ඞཁͳ৔߹ 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
  13. func fetchFirstResult() async throws -> ExamResult? { let context =

    ModelContext(modelContainer) return try context .fetch(FetchDescriptor<ExamResult>()) .first } ࢀর͕ඞཁͳ৔߹ 34 /PO4FOEBCMF
  14. func fetchFirstResultID() async throws -> PersistentIdentifier? { let context =

    ModelContext(modelContainer) return try context .fetchIdentifiers(FetchDescriptor<ExamResult>()) .first } 1FSTJTUFOU*EFOUJGJFSΛ࢖͏ 35 4FOEBCMF"
  15. 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 } }
  16. 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 } }
  17. ͜ͷίʔυ͸ฒߦ҆શͰ͸ͳ͍ 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ϞʔυͰίϯύΠϧՄೳ
  18. 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 = ??? }
  19. 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 ࣌ؒ
  20. 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 = ??? }
  21. શͯͷϓϩύςΟ্͕ॻ͖͞ΕΔ 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
  22. ू໿͢Δ"DUPSͷൺֱ 60 .BJO"DUPS (MPCBM"DUPS ϝϦοτ w 6*ʹಉظతʹ ৘ใΛ౉ͤΔ w ࣮૷͕

    ͱͯ΋؆୯ 6*εϨουΛ ϒϩοΫ͠ͳ͍ σϝϦοτ 6*εϨουΛ ϒϩοΫ͢Δ 6*ͱͷ΍ΓͱΓ͕ ඇಉظʹͳΔ
  23. ू໿͢Δ"DUPSͷൺֱ 61 .BJO"DUPS (MPCBM"DUPS ϝϦοτ w 6*ʹಉظతʹ ৘ใΛ౉ͤΔ w ࣮૷͕

    ͱͯ΋؆୯ 6*εϨουΛ ϒϩοΫ͠ͳ͍ σϝϦοτ 6*εϨουΛ ϒϩοΫ͢Δ 6*ͱͷ΍ΓͱΓ͕ ඇಉظʹͳΔ Φεεϝ
  24. 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
  25. 3%#ͰΑ͘࢖ΘΕΔΞΠσΞ 72 w .PEFM΍ΦϒδΣΫτ͝ͱʹ"DUPSΛ෼͚Δ w ϩοΫͷ࣮૷ˠ#σουϩοΫͷةݥੑ# @Model class ModelA {

    var a: Int } @Model class ModelB { var b: String } "DUPS" "DUPS# 5SBOTBDUJPO 5SBOTBDUJPO # #
  26. ୯Ұॻࠐ ಉ࣌ಡࠐ 74 ಡΈࠐΈ ॻ͖ࠐΈ ॻ͖ࠐΈ ॻ͖ࠐΈ ಡΈࠐΈ ಡΈࠐΈ ಡΈࠐΈ

    ಡΈࠐΈ ಡΈࠐΈ ಡΈࠐΈ ಡΈࠐΈ ࣌ؒ ಉ࣌ʹ͚ͭͩ ෳ਺ಉ࣮࣌ߦՄ
  27. 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