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

(iOSDC Japan 2023) ActorでCoreDataをスレッドから解放しよう

Himeshi
September 05, 2023

(iOSDC Japan 2023) ActorでCoreDataをスレッドから解放しよう

このスライドは、iOSDC Japan 2023 でトークを行ったものです。
https://fortee.jp/iosdc-japan-2023/proposal/240c16ac-498a-4d17-a43a-f34f0fdbe041

Himeshi

September 05, 2023
Tweet

More Decks by Himeshi

Other Decks in Programming

Transcript

  1. ͜ͷίʔυ͸҆શͳ࣮૷͔ʁ 9 class CoreDataController { private var entity: CoreDataEntity? =

    nil private let moc: NSManagedObjectContext init(container: NSPersistentContainer) { self.moc = container.newBackgroundContext() } func configure() throws { entity = CoreDataEntity(context: moc) try moc.save() } }
  2. 10 class CoreDataController { private var entity: CoreDataEntity? = nil

    private let moc: NSManagedObjectContext init(container: NSPersistentContainer) { self.moc = container.newBackgroundContext() } func configure() throws { entity = CoreDataEntity(context: moc) try moc.save() } } .BOBHFEPCKFDUDPOUFYUΛൃߦ .BOBHFEPCKFDUΛੜ੒ɺอଘ ͜ͷίʔυ͸҆શͳ࣮૷͔ʁ
  3. ͜ͷ࣮૷͸εϨουηʔϑͰ͸ͳ͍ 11 class CoreDataController { private var entity: CoreDataEntity? =

    nil private let moc: NSManagedObjectContext init(container: NSPersistentContainer) { self.moc = container.newBackgroundContext() } func configure() throws { entity = CoreDataEntity(context: moc) try moc.save() } } initͱҟͳΔεϨου͔Β ΞΫηεͯ͠͸͍͚ͳ͍
  4. ͜ͷ࣮૷͸εϨουηʔϑͰ͸ͳ͍ 12 class CoreDataController { private var entity: CoreDataEntity? =

    nil private let moc: NSManagedObjectContext init(container: NSPersistentContainer) { self.moc = container.newBackgroundContext() } func configure() throws { entity = CoreDataEntity(context: moc) try moc.save() } } /4.BOBHFE0CKFDUͱ/4.BOBHFE0CKFDU$POUFYU͸ εϨουηʔϑͰ͸ͳ͍ɻ
  5. ͜ͷ࣮૷͸εϨουηʔϑͰ͸ͳ͍ 13 class CoreDataController { private var entity: CoreDataEntity? =

    nil private let moc: NSManagedObjectContext init(container: NSPersistentContainer) { self.moc = container.newBackgroundContext() } func configure() throws { entity = CoreDataEntity(context: moc) try moc.save() } } εϨουηʔϑͰ͸ͳ͍ΦϒδΣΫτΛ ϓϩύςΟͱͯ͠อ͍࣋ͯ͠Δɻ ˠҟͳΔεϨου͔ΒΞΫηε͢ΔͱεϨουҧ൓ /4.BOBHFE0CKFDUͱ/4.BOBHFE0CKFDU$POUFYU͸ εϨουηʔϑͰ͸ͳ͍ɻ
  6. "DUPSʹ͢Ε͹େৎ෉ʁ 16 actor CoreDataController { private var entity: CoreDataEntity? =

    nil private let moc: NSManagedObjectContext init(container: NSPersistentContainer) { self.moc = container.newBackgroundContext() } func configure() throws { entity = CoreDataEntity(context: moc) try moc.save() } }
  7. 17 w "DUPSʹ͢Ε͹σʔλڝ߹͠ͳ͍͸ͣʜ actor CoreDataController { private var entity: CoreDataEntity?

    = nil private let moc: NSManagedObjectContext init(container: NSPersistentContainer) { self.moc = container.newBackgroundContext() } func configure() throws { entity = CoreDataEntity(context: moc) try moc.save() } } "DUPSʹ͢Ε͹େৎ෉ʁ
  8. 18 w "DUPSʹ͢Ε͹σʔλڝ߹͠ͳ͍͸ͣʜ actor CoreDataController { private var entity: CoreDataEntity?

    = nil private let moc: NSManagedObjectContext init(container: NSPersistentContainer) { self.moc = container.newBackgroundContext() } func configure() throws { entity = CoreDataEntity(context: moc) try moc.save() } } ͜ͷ࣮૷͸$PODVSSFODZXBSOJOHΛग़͞ͳ͍ɻ "DUPSʹ͢Ε͹େৎ෉ʁ
  9. actor CoreDataController { private var entity: CoreDataEntity? = nil private

    let moc: NSManagedObjectContext init(container: NSPersistentContainer) { self.moc = container.newBackgroundContext() } func configure() throws { entity = CoreDataEntity(context: moc) try moc.save() } } 19 w "DUPSCPVOEBSZ֎͔Β͜ͷBDUPSΛ࢖ͬͯΈΔɻ func testCallCoreDataController() async throws { let controller = CoreDataController( container: container ) try await controller.configure() } "DUPSʹ͢Ε͹େৎ෉ʁ
  10. actor CoreDataController { private var entity: CoreDataEntity? = nil private

    let moc: NSManagedObjectContext init(container: NSPersistentContainer) { self.moc = container.newBackgroundContext() } func configure() throws { entity = CoreDataEntity(context: moc) try moc.save() } } 20 w ͜ͷ࣮૷͸εϨουҧ൓ΛҾ͖ى͜͢ɻ ͜͜ͰΫϥογϡ "DUPSʹ͢Ε͹େৎ෉ʁ
  11. actor CoreDataController { private var entity: CoreDataEntity? = nil private

    let moc: NSManagedObjectContext init(container: NSPersistentContainer) { self.moc = container.newBackgroundContext() } func configure() throws { entity = CoreDataEntity(context: moc) try moc.save() } } "DUPS͸εϨουಉҰੑΛอূ͠ͳ͍ 21 w JOJUͱDPOpHVSF͕ݺ͹ΕΔεϨου͸ɺ࣮ࡍʹ͸ҟͳΔɻ func testCallCoreDataController() async throws { let controller = CoreDataController( container: container ) try await controller.configure() }
  12. class CoreDataController { private var entityID: NSManagedObjectID? = nil private

    let container: NSPersistentContainer func update(_ newName: String) async throws { guard let entityID else { return } let moc = container.newBackgroundContext() try await moc.perform { let entity = try? moc.existingObject(with: entityID) as? CoreDataEntity entity?.name = newName try moc.save() } } } /4.BOBHFE0CKFDU*%Λ࢖͏ 24
  13. class CoreDataController { private var entityID: NSManagedObjectID? = nil private

    let container: NSPersistentContainer func update(_ newName: String) async throws { guard let entityID else { return } let moc = container.newBackgroundContext() try await moc.perform { let entity = try? moc.existingObject(with: entityID) as? CoreDataEntity entity?.name = newName try moc.save() } } } /4.BOBHFE0CKFDU*%Λ࢖͏ 25 ࣮ࡍͷNBOBHFEPCKFDUͷܕͷ͔ΘΓʹ /4.BOBHFE0CKFDU*%Λ࢖༻ .BOBHFEPCKFDUʹΞΫηε͢Δʹ͸GFUDI͢Δ
  14. class CoreDataController { private var entityID: NSManagedObjectID? = nil private

    let container: NSPersistentContainer func update(_ newName: String) async throws { guard let entityID else { return } let moc = container.newBackgroundContext() try await moc.perform { let entity = try? moc.existingObject(with: entityID) as? CoreDataEntity entity?.name = newName try moc.save() } } } /4.BOBHFE0CKFDU*%Λ࢖͏ 26 .BOBHFE0CKFDUʹΞΫηε͢Δʹ͸ ຖճGFUDI͠ͳ͚Ε͹ͳΒͳ͍ .BOBHFE0CKFDUͷܕ৘ใ͕ͳ͍
  15. .0$ʹHMPCBMBDUPSΛద༻ w $PSF%BUB"DUPSΛద༻ͨ͠ϥού $".BOBHFE0CKFDU$POUFYU  Λ༻ҙ͠ɺϥούܦ༝Ͱ.0$Λར༻͢Δɻ 39 @CoreDataActor final class

    CAManagedObjectContext { private let moc: NSManagedObjectContext init(_ moc: NSManagedObjectContext) { self.moc = moc } func fetch<…>(…) throws -> [T] { … } func save() throws { try moc.save() } func delete(_ object: NSManagedObject) { … } func insert<T: NSManagedObject>() -> T { … } }
  16. "DUPSͷ࣮ߦ؀ڥ 43 actor MyActor { … } w "DUPS͸ɺσʔλڝ߹͕ൃੜ͠ͳ͍͜ͱΛอূ͢Δɻ w

    ಉظతʹ࣮ߦ͢Δ໋ྩͷ·ͱ·ΓͰ͋Δʮ+PCʯΛҰ࣮ͭͣͭߦɻ
  17. "DUPSͷ࣮ߦ؀ڥ 44 actor MyActor { … } w "DUPS͸ɺσʔλڝ߹͕ൃੜ͠ͳ͍͜ͱΛอূ͢Δɻ w

    ಉظతʹ࣮ߦ͢Δ໋ྩͷ·ͱ·ΓͰ͋Δʮ+PCʯΛҰ࣮ͭͣͭߦɻ w ʮ+PCʯΛͲͷΑ͏ʹ࣮ߦ͢Δ͔͸ఆٛ͞Ε͍ͯͳ͍ɻ w σϑΥϧτͰ͸ɺ4XJGUϥϯλΠϜ͕+PCͷ࣮ߦ؀ڥΛఏڙ͢Δɻ w +PC͕࣮ߦ͞ΕΔεϨουࣗମ͸ຖճมΘΔՄೳੑ͕͋Δɻ
  18. $VTUPN"DUPS&YFDVUPSͱ͸ w 4& 4XJGUͰ࣮૷  w "DUPSͷʮ+PCΛͲ͏࣮ߦ͢Δͷ͔ʯΛΧελϚΠζͰ͖Δɻ w +PCΛ࣮ߦ͢Δ&YFDVUPSΛ࣮૷͢Δɻ w

    VOPXOFE&YFDVUPSͱͯ͠ɺ+PCΛ࣮ߦ͢Δ&YFDVUPSΛొ࿥͢Δɻ 45 actor MyActor { nonisolated var unownedExecutor: UnownedSerialExecutor { ... } }
  19. $PSF%BUB&YFDVUPSͷ࣮૷ w KPCΛ/4.BOBHFE0CKFDU$POUFYUQFSGPSN @ Ͱ࣮ߦ͢Δɻ 47 final class CoreDataExecutor: SerialExecutor

    { private let container: MOCContainer func enqueue(_ job: consuming ExecutorJob) { let unownedJob = UnownedJob(job) container.perform { unownedJob.runSynchronously( on: self.asUnownedSerialExecutor() ) } } }
  20. $PSF%BUB&YFDVUPSͷ࣮૷ 48 final class CoreDataExecutor: SerialExecutor { private let container:

    MOCContainer func enqueue(_ job: consuming ExecutorJob) { let unownedJob = UnownedJob(job) container.perform { unownedJob.runSynchronously( on: self.asUnownedSerialExecutor() ) } } } w KPCΛ/4.BOBHFE0CKFDU$POUFYUQFSGPSN @ Ͱ࣮ߦ͢Δɻ +PCΛQFSGPSNؔ਺಺Ͱ࣮ߦ
  21. $PSF%BUB&YFDVUPSͷ࣮૷ 49 final class CoreDataExecutor: SerialExecutor { private let container:

    MOCContainer func enqueue(_ job: consuming ExecutorJob) { let unownedJob = UnownedJob(job) container.perform { unownedJob.runSynchronously( on: self.asUnownedSerialExecutor() ) } } } w KPCΛ/4.BOBHFE0CKFDU$POUFYUQFSGPSN @ Ͱ࣮ߦ͢Δɻ &YFDVUPS+PC͸/PODPQZBCMFUZQF ʢ4XJGUͰಋೖʣ
  22. $PSF%BUB&YFDVUPSͷ࣮૷ 50 final class CoreDataExecutor: SerialExecutor { private let container:

    MOCContainer func enqueue(_ job: consuming ExecutorJob) { let unownedJob = UnownedJob(job) container.perform { unownedJob.runSynchronously( on: self.asUnownedSerialExecutor() ) } } } w 4FSJBM&YFDVUPS͸4FOEBCMFͰ͋Δ͜ͱΛཁٻ͢Δɻ /4.BOBHFE0CKFDU$POUFYUΛ 4FOEBCMFͳ΋ͷͱͯ͠ѻ͏ͨΊͷίϯςφ
  23. .0$$POUBJOFSͷ࣮૷ w .0$ͷQFSGPSNΛεϨουηʔϑʹݺͿͨΊͷίϯςφ 51 final class MOCContainer: @unchecked Sendable {

    private let persistentContainer: NSPersistentContainer let queue = DispatchQueue(label: "MOCContainer") lazy var moc: NSManagedObjectContext = persistentContainer.newBackgroundContext() init(_ persistentContainer: NSPersistentContainer) { self.persistentContainer = persistentContainer } func perform(_ block: @escaping @Sendable () -> Void) { queue.async { self.moc.perform(block) } } }
  24. $PSF%BUB&YFDVUPS͖ͭͮ w (MPCBMBDUPSʹ&YFDVUPSΛొ࿥͢ΔͨΊʹɺTIBSFEΠϯελϯεΛ ༻ҙ͢Δɻ 52 extension CoreDataExecutor { @CoreDataActor var

    moc: CAManagedObjectContext { .init(container.moc) } static var sharedExecutor = CoreDataExecutor.init(.shared) static var sharedUnownedExecutor: UnownedSerialExecutor { sharedExecutor.asUnownedSerialExecutor() } }
  25. $SFBUFૢ࡞ʢ$PSF%BUB"DUPS಺ʣ w .0$͸ɺ$PSF%BUB&YFDVUPS͕ఏڙ͢Δ $".BOBHFE0CKFDU$POUFYUΦϒδΣΫτΛ༻͍Δɻ w $PSF%BUB"DUPS಺ͳͷͰɺಉظతʹૢ࡞Ͱ͖Δɻ 58 @CoreDataActor func createObject()

    throws -> Entity { let moc = CoreDataExecutor.sharedExecutor.moc let managedObject: Entity = moc.insert() try moc.save() return managedObject }
  26. @AnotherActor func createObject() throws -> Entity { let moc =

    CoreDataExecutor.sharedExecutor.moc let managedObject: Entity = moc.insert() try moc.save() return managedObject } $SFBUFૢ࡞ʢ$PSF%BUB"DUPS֎ʣ 59 ίϯύΠϧΤϥʔ BXBJU͕ඞཁ w .BOBHFEPCKFDU͓Αͼ.0$͸ɺඇಉظΞΫηε͕ڧ੍͞ΕΔ
  27. $SFBUFૢ࡞ʢ$PSF%BUB"DUPS֎ʣ 60 @AnotherActor func createObject() async throws -> Entity {

    let moc = await CoreDataExecutor.sharedExecutor.moc let managedObject: Entity = await moc.insert() try await moc.save() return managedObject } w .BOBHFEPCKFDU͓Αͼ.0$͸ɺඇಉظΞΫηε͕ڧ੍͞ΕΔ
  28. 3FBEૢ࡞ʢ$PSF%BUB"DUPS಺ʣ 61 @CoreDataActor func fetchObject() throws -> Entity? { let

    moc = CoreDataExecutor.sharedExecutor.moc let fetchRequest = Entity.fetchRequest() fetchRequest.entity = Entity.entity() let managedObjects = try moc.fetch(fetchRequest) return managedObjects.first } w .0$͸ɺ$PSF%BUB&YFDVUPS͕ఏڙ͢Δ $".BOBHFE0CKFDU$POUFYUΦϒδΣΫτΛ༻͍Δɻ w $PSF%BUB"DUPS಺ͳͷͰɺಉظతʹૢ࡞Ͱ͖Δɻ
  29. 3FBEૢ࡞ʢ$PSF%BUB"DUPS֎ʣ w /4'FUDI3FRVFTU͸4FOEBCMFͰͳ͍ɻ w $PSF%BUB"DUPS಺Ͱ૊Έཱͯ.0$ʹ౉͢͜ͱ͕ඞཁɻ 62 @AnotherActor func fetchObject() async

    throws -> Entity? { let moc = await CoreDataExecutor.sharedExecutor.moc let managedObjects = try await Task { @CoreDataActor in let fetchRequest = Entity.fetchRequest() fetchRequest.entity = Entity.entity() return try moc.fetch(fetchRequest) }.value return managedObjects.first }
  30. 6QEBUFૢ࡞ʢ$PSF%BUB"DUPS಺ʣ 63 @CoreDataActor func updateObject(managedObject: Entity) throws { // nameΛupdate͢Δ

    let moc = CoreDataExecutor.sharedExecutor.moc managedObject.name = "Jiro" try moc.save() } w .0$͸ɺ$PSF%BUB&YFDVUPS͕ఏڙ͢Δ $".BOBHFE0CKFDU$POUFYUΦϒδΣΫτΛ༻͍Δɻ w $PSF%BUB"DUPS಺ͳͷͰɺಉظతʹૢ࡞Ͱ͖Δɻ
  31. 6QEBUFૢ࡞ʢ$PSF%BUB"DUPS֎ʣ w 4FUUFS͸BDUPS֎ʹެ։͞Εͳ͍ͨΊɺ$PSF%BUB"DUPS಺Ͱͷૢ࡞ ͕ڧ੍͞ΕΔɻ 64 @AnotherActor func updateObject(managedObject: Entity) async

    throws { // nameΛupdate͢Δ try await Task { @CoreDataActor in let moc = CoreDataExecutor.sharedExecutor.moc managedObject.name = "Jiro" try moc.save() }.value }
  32. %FMFUFૢ࡞ʢ$PSF%BUB"DUPS಺ʣ 65 @CoreDataActor func deleteObject(managedObject: Entity) throws { let moc

    = CoreDataExecutor.sharedExecutor.moc moc.delete(managedObject) try moc.save() } w .0$͸ɺ$PSF%BUB&YFDVUPS͕ఏڙ͢Δ $".BOBHFE0CKFDU$POUFYUΦϒδΣΫτΛ༻͍Δɻ w $PSF%BUB"DUPS಺ͳͷͰɺಉظతʹૢ࡞Ͱ͖Δɻ
  33. %FMFUFૢ࡞ʢ$PSF%BUB"DUPS֎ʣ 66 @AnotherActor func deleteObject(managedObject: Entity) async throws { let

    moc = await CoreDataExecutor.sharedExecutor.moc await moc.delete(managedObject) try await moc.save() } w .BOBHFEPCKFDU͓Αͼ.0$͸ɺඇಉظΞΫηε͕ڧ੍͞ΕΔ
  34. ࠷ॳͷίʔυ͸Ͳ͏ͳͬͨͷ͔ 67 class CoreDataController { private var entity: CoreDataEntity? =

    nil private let moc: NSManagedObjectContext init(container: NSPersistentContainer) { self.moc = container.newBackgroundContext() } func configure() throws { entity = CoreDataEntity(context: moc) try moc.save() } }
  35. ͜͏ͳΓ·ͨ͠ 68 @CoreDataActor class CoreDataController { private var entity: CoreDataEntity?

    = nil func configure() throws { let moc = CoreDataExecutor.sharedExecutor.moc entity = moc.insert() try moc.save() } }
  36. ͜͏ͳΓ·ͨ͠ 69 @CoreDataActor class CoreDataController { private var entity: CoreDataEntity?

    = nil func configure() throws { let moc = CoreDataExecutor.sharedExecutor.moc entity = moc.insert() try moc.save() } } w $MBTTࣗମΛ$PSF%BUB"DUPSʹॴଐͤ͞Δɻ w ҆શʹNBOBHFEPCKFDUΛϓϩύςΟͱͯ͠อ࣋ɻ