use a background context for core data ops in dataservice

This commit is contained in:
Satindar Dhillon
2022-04-21 17:31:06 -07:00
parent b494698b68
commit c310aa38fd
13 changed files with 136 additions and 111 deletions

View File

@ -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
}

View File

@ -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)")
}
}
}
}

View File

@ -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<Models.Highlight> = Highlight.fetchRequest()
let fetchRequest: NSFetchRequest<Models.Highlight> = 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")
}
}
}

View File

@ -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
)
}

View File

@ -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()))

View File

@ -59,19 +59,20 @@ public extension DataService {
}
func deletePersistedHighlight(objectID: String) {
let context = persistentContainer.viewContext
let fetchRequest: NSFetchRequest<Models.Highlight> = Highlight.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "id == %@", objectID)
for highlight in (try? context.fetch(fetchRequest)) ?? [] {
context.delete(highlight)
}
backgroundContext.perform {
let fetchRequest: NSFetchRequest<Models.Highlight> = 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)")
}
}
}
}

View File

@ -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
)

View File

@ -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: _):

View File

@ -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
)

View File

@ -49,15 +49,16 @@ public extension DataService {
switch payload.data {
case let .saved(highlight: highlight):
let context = self.persistentContainer.viewContext
let fetchRequest: NSFetchRequest<Models.Highlight> = Highlight.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "id == %@", highlight.id)
let itemID = (try? context.fetch(fetchRequest))?.first?.linkedItemId ?? ""
self.backgroundContext.perform {
let fetchRequest: NSFetchRequest<Models.Highlight> = 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)))

View File

@ -76,26 +76,28 @@ public extension DataService {
extension DataService {
func persistArticleContent(htmlContent: String, slug: String, highlights: [InternalHighlight]) {
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
fetchRequest.predicate = NSPredicate(
format: "slug == %@", slug
)
backgroundContext.perform {
let fetchRequest: NSFetchRequest<Models.LinkedItem> = 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)")
}
}
}
}

View File

@ -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<Models.LinkedItem> = 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<Models.LinkedItem> = 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
}
}

View File

@ -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<Models.Highlight> = 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<Models.Highlight> = 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)")
}
}
}