From c310aa38fd71d388321bd09a176f56a2f71f3b8e Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 21 Apr 2022 17:31:06 -0700 Subject: [PATCH] use a background context for core data ops in dataservice --- .../Models/CoreData/StorageProvider.swift | 2 - .../Sources/Models/DataModels/FeedItem.swift | 80 +++++++++++-------- .../Services/DataService/DataService.swift | 23 +++--- .../DataService/Mutations/ArchiveLink.swift | 4 +- .../Mutations/CreateHighlight.swift | 2 +- .../Mutations/DeleteHighlight.swift | 25 +++--- .../Mutations/MergeHighlight.swift | 4 +- .../DataService/Mutations/RemoveLink.swift | 4 +- .../UpdateArticleReadingProgress.swift | 4 +- .../Mutations/UpdateHighlightAttributes.swift | 17 ++-- .../Queries/ArticleContentQuery.swift | 34 ++++---- .../Queries/LibraryItemsQuery.swift | 16 ++-- .../InternalModels/InternalHighlight.swift | 32 ++++---- 13 files changed, 136 insertions(+), 111 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift index f74c220ed..43c958f57 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift +++ b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift @@ -15,8 +15,6 @@ public class PersistentContainer: NSPersistentContainer { container.viewContext.automaticallyMergesChangesFromParent = true container.viewContext.name = "viewContext" container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump - container.viewContext.undoManager = nil - container.viewContext.shouldDeleteInaccessibleFaults = true return container } diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift index b816170b0..979fa6857 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift @@ -194,17 +194,21 @@ public extension FeedItemDep { public extension Sequence where Element == FeedItemDep { func persist(context: NSManagedObjectContext) -> [LinkedItem]? { - let linkedItems = map { $0.asManagedObject(inContext: context) } + var result: [LinkedItem]? - do { - try context.save() - logger.debug("LinkedItems saved succesfully") - return linkedItems - } catch { - context.rollback() - logger.debug("Failed to save LinkedItems: \(error.localizedDescription)") - return nil + context.performAndWait { + let linkedItems = map { $0.asManagedObject(inContext: context) } + + do { + try context.save() + result = linkedItems + logger.debug("LinkedItems saved succesfully") + } catch { + context.rollback() + logger.debug("Failed to save LinkedItems: \(error.localizedDescription)") + } } + return result } } @@ -215,7 +219,13 @@ public extension LinkedItem { format: "id == %@", itemID ) - return (try? context.fetch(fetchRequest))?.first + var item: LinkedItem? + + context.performAndWait { + item = (try? context.fetch(fetchRequest))?.first + } + + return item } func update( @@ -224,38 +234,42 @@ public extension LinkedItem { newAnchorIndex: Int? = nil, newIsArchivedValue: Bool? = nil ) { - if let newReadingProgress = newReadingProgress { - readingProgress = newReadingProgress - } + context.perform { + if let newReadingProgress = newReadingProgress { + self.readingProgress = newReadingProgress + } - if let newAnchorIndex = newAnchorIndex { - readingProgressAnchor = Int64(newAnchorIndex) - } + if let newAnchorIndex = newAnchorIndex { + self.readingProgressAnchor = Int64(newAnchorIndex) + } - if let newIsArchivedValue = newIsArchivedValue { - isArchived = newIsArchivedValue - } + if let newIsArchivedValue = newIsArchivedValue { + self.isArchived = newIsArchivedValue + } - guard context.hasChanges else { return } + guard context.hasChanges else { return } - do { - try context.save() - logger.debug("LinkedItem updated succesfully") - } catch { - context.rollback() - logger.debug("Failed to update LinkedItem: \(error.localizedDescription)") + do { + try context.save() + logger.debug("LinkedItem updated succesfully") + } catch { + context.rollback() + logger.debug("Failed to update LinkedItem: \(error.localizedDescription)") + } } } func remove(inContext context: NSManagedObjectContext) { - context.delete(self) + context.perform { + context.delete(self) - do { - try context.save() - logger.debug("LinkedItem removed") - } catch { - context.rollback() - logger.debug("Failed to remove LinkedItem: \(error.localizedDescription)") + do { + try context.save() + logger.debug("LinkedItem removed") + } catch { + context.rollback() + logger.debug("Failed to remove LinkedItem: \(error.localizedDescription)") + } } } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 4c0ca1ab2..ee694d5d8 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -28,6 +28,7 @@ public final class DataService: ObservableObject { self.networker = networker self.persistentContainer = PersistentContainer.make() self.backgroundContext = persistentContainer.newBackgroundContext() + backgroundContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump persistentContainer.loadPersistentStores { _, error in if let error = error { @@ -43,20 +44,22 @@ public final class DataService: ObservableObject { } public func clearHighlights() { - deletedHighlightsIDs.removeAll() + backgroundContext.perform { + self.deletedHighlightsIDs.removeAll() - let fetchRequest: NSFetchRequest = Highlight.fetchRequest() + let fetchRequest: NSFetchRequest = Highlight.fetchRequest() - let highlights = (try? persistentContainer.viewContext.fetch(fetchRequest)) ?? [] + let highlights = (try? self.backgroundContext.fetch(fetchRequest)) ?? [] - for highlight in highlights { - persistentContainer.viewContext.delete(highlight) - } + for highlight in highlights { + self.backgroundContext.delete(highlight) + } - do { - try persistentContainer.viewContext.save() - } catch { - logger.debug("failed to delete objects") + do { + try self.backgroundContext.save() + } catch { + logger.debug("failed to delete objects") + } } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/ArchiveLink.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/ArchiveLink.swift index 36603ba42..389f12f04 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/ArchiveLink.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/ArchiveLink.swift @@ -44,9 +44,9 @@ public extension DataService { switch payload.data { case let .success(linkId): - if let linkedItem = LinkedItem.lookup(byID: itemID, inContext: self.persistentContainer.viewContext) { + if let linkedItem = LinkedItem.lookup(byID: itemID, inContext: self.backgroundContext) { linkedItem.update( - inContext: self.persistentContainer.viewContext, + inContext: self.backgroundContext, newIsArchivedValue: archived ) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift index bcad48562..c8ad14632 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift @@ -55,7 +55,7 @@ public extension DataService { switch payload.data { case let .saved(highlight: highlight): _ = highlight.persist( - context: self.persistentContainer.viewContext, + context: self.backgroundContext, associatedItemID: articleId ) promise(.success(highlight.encoded())) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/DeleteHighlight.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/DeleteHighlight.swift index 65f8ce947..1461437e6 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/DeleteHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/DeleteHighlight.swift @@ -59,19 +59,20 @@ public extension DataService { } func deletePersistedHighlight(objectID: String) { - let context = persistentContainer.viewContext - let fetchRequest: NSFetchRequest = Highlight.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "id == %@", objectID) - for highlight in (try? context.fetch(fetchRequest)) ?? [] { - context.delete(highlight) - } + backgroundContext.perform { + let fetchRequest: NSFetchRequest = Highlight.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "id == %@", objectID) + for highlight in (try? self.backgroundContext.fetch(fetchRequest)) ?? [] { + self.backgroundContext.delete(highlight) + } - do { - try context.save() - print("Highlight deleted succesfully") - } catch { - context.rollback() - print("Failed to delete Highlight: \(error.localizedDescription)") + do { + try self.backgroundContext.save() + print("Highlight deleted succesfully") + } catch { + self.backgroundContext.rollback() + print("Failed to delete Highlight: \(error.localizedDescription)") + } } } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift index eac9bedc8..c892ed01b 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift @@ -58,8 +58,8 @@ public extension DataService { switch payload.data { case let .saved(highlight: highlight): - _ = highlight.persist( - context: self.persistentContainer.viewContext, + highlight.persist( + context: self.backgroundContext, associatedItemID: articleId, oldHighlightsIds: overlapHighlightIdList ) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLink.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLink.swift index 1fb4fc299..511d43efe 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLink.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLink.swift @@ -51,8 +51,8 @@ public extension DataService { switch payload.data { case let .success(item): - if let linkedItem = LinkedItem.lookup(byID: itemID, inContext: self.persistentContainer.viewContext) { - linkedItem.remove(inContext: self.persistentContainer.viewContext) + if let linkedItem = LinkedItem.lookup(byID: itemID, inContext: self.backgroundContext) { + linkedItem.remove(inContext: self.backgroundContext) } promise(.success(item)) case .error(errorCode: _): diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleReadingProgress.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleReadingProgress.swift index d7b3d8e9f..9ead9107a 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleReadingProgress.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleReadingProgress.swift @@ -51,9 +51,9 @@ public extension DataService { switch payload.data { case let .saved(feedItem): - if let linkedItem = LinkedItem.lookup(byID: itemID, inContext: self.persistentContainer.viewContext) { + if let linkedItem = LinkedItem.lookup(byID: itemID, inContext: self.backgroundContext) { linkedItem.update( - inContext: self.persistentContainer.viewContext, + inContext: self.backgroundContext, newReadingProgress: readingProgress, newAnchorIndex: anchorIndex ) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift index 27754240f..ef16ad02a 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift @@ -49,15 +49,16 @@ public extension DataService { switch payload.data { case let .saved(highlight: highlight): - let context = self.persistentContainer.viewContext - let fetchRequest: NSFetchRequest = Highlight.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "id == %@", highlight.id) - let itemID = (try? context.fetch(fetchRequest))?.first?.linkedItemId ?? "" + self.backgroundContext.perform { + let fetchRequest: NSFetchRequest = Highlight.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "id == %@", highlight.id) + let itemID = (try? self.backgroundContext.fetch(fetchRequest))?.first?.linkedItemId ?? "" - _ = highlight.persist( - context: self.persistentContainer.viewContext, - associatedItemID: itemID - ) + highlight.persist( + context: self.backgroundContext, + associatedItemID: itemID + ) + } promise(.success(highlight.id)) case let .error(errorCode: errorCode): promise(.failure(.message(messageText: errorCode.rawValue))) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift index 686f4c0b6..458f3be42 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift @@ -76,26 +76,28 @@ public extension DataService { extension DataService { func persistArticleContent(htmlContent: String, slug: String, highlights: [InternalHighlight]) { - let fetchRequest: NSFetchRequest = LinkedItem.fetchRequest() - fetchRequest.predicate = NSPredicate( - format: "slug == %@", slug - ) + backgroundContext.perform { + let fetchRequest: NSFetchRequest = LinkedItem.fetchRequest() + fetchRequest.predicate = NSPredicate( + format: "slug == %@", slug + ) - let linkedItem = try? persistentContainer.viewContext.fetch(fetchRequest).first + let linkedItem = try? self.backgroundContext.fetch(fetchRequest).first - if let linkedItem = linkedItem, let linkedItemID = linkedItem.id { - _ = highlights.map { - $0.asManagedObject(context: persistentContainer.viewContext, associatedItemID: linkedItemID) + if let linkedItem = linkedItem, let linkedItemID = linkedItem.id { + _ = highlights.map { + $0.asManagedObject(context: self.backgroundContext, associatedItemID: linkedItemID) + } + linkedItem.htmlContent = htmlContent } - linkedItem.htmlContent = htmlContent - } - do { - try persistentContainer.viewContext.save() - print("ArticleContent saved succesfully") - } catch { - persistentContainer.viewContext.rollback() - print("Failed to save ArticleContent: \(error)") + do { + try self.backgroundContext.save() + print("ArticleContent saved succesfully") + } catch { + self.backgroundContext.rollback() + print("Failed to save ArticleContent: \(error)") + } } } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift index 0e150dc11..28050516a 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift @@ -62,7 +62,7 @@ public extension DataService { switch payload.data { case let .success(result: result): // save items to coredata - _ = result.items.persist(context: self.persistentContainer.viewContext) + _ = result.items.persist(context: self.backgroundContext) promise(.success(result)) case .error: promise(.failure(.unknown)) @@ -78,10 +78,16 @@ public extension DataService { } func cachedFeedItems() -> [FeedItemDep] { - let fetchRequest: NSFetchRequest = LinkedItem.fetchRequest() - fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \LinkedItem.savedAt, ascending: false)] - let items = (try? persistentContainer.viewContext.fetch(fetchRequest)) ?? [] - return items.map { FeedItemDep.make(from: $0) } + var result = [FeedItemDep]() + + backgroundContext.performAndWait { + let fetchRequest: NSFetchRequest = LinkedItem.fetchRequest() + fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \LinkedItem.savedAt, ascending: false)] + let items = (try? backgroundContext.fetch(fetchRequest)) ?? [] + result = items.map { FeedItemDep.make(from: $0) } + } + + return result } } diff --git a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalHighlight.swift b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalHighlight.swift index ca0c25231..7cb9594bb 100644 --- a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalHighlight.swift @@ -50,25 +50,25 @@ struct InternalHighlight: Encodable { context: NSManagedObjectContext, associatedItemID: String, oldHighlightsIds: [String] = [] - ) -> Highlight? { - let highlight = asManagedObject(context: context, associatedItemID: associatedItemID) + ) { + context.perform { + _ = asManagedObject(context: context, associatedItemID: associatedItemID) - if !oldHighlightsIds.isEmpty { - let fetchRequest: NSFetchRequest = Highlight.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "id IN %@", oldHighlightsIds) - for highlight in (try? context.fetch(fetchRequest)) ?? [] { - context.delete(highlight) + if !oldHighlightsIds.isEmpty { + let fetchRequest: NSFetchRequest = Highlight.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "id IN %@", oldHighlightsIds) + for highlight in (try? context.fetch(fetchRequest)) ?? [] { + context.delete(highlight) + } } - } - do { - try context.save() - print("Highlight saved succesfully") - return highlight - } catch { - context.rollback() - print("Failed to save Highlight: \(error.localizedDescription)") - return nil + do { + try context.save() + print("Highlight saved succesfully") + } catch { + context.rollback() + print("Failed to save Highlight: \(error.localizedDescription)") + } } }