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

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

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Himeshi Himeshi
September 05, 2023

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

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

Avatar for Himeshi

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ΛϓϩύςΟͱͯ͠อ࣋ɻ