From 656a99fe1240b94198d70885f1a17c1e75e2f595 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Fri, 11 Sep 2015 13:59:28 +0900 Subject: [PATCH] added thread safety checks to ListMonitor to prevent deadlocks after calling refetch() --- CoreStore/Observing/ListMonitor.swift | 97 +++++++++++++++++++++++-- CoreStore/Observing/ObjectMonitor.swift | 2 + 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/CoreStore/Observing/ListMonitor.swift b/CoreStore/Observing/ListMonitor.swift index b6d7a13..4b32706 100644 --- a/CoreStore/Observing/ListMonitor.swift +++ b/CoreStore/Observing/ListMonitor.swift @@ -114,6 +114,11 @@ public final class ListMonitor { */ public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> T? { + CoreStore.assert( + !self.isPendingRefetch || NSThread.isMainThread(), + "Attempted to access a \(typeName(self)) outside the main thread while a refetch is in progress." + ) + guard let sections = self.fetchedResultsController.sections where sectionIndex < sections.count else { @@ -136,6 +141,11 @@ public final class ListMonitor { */ public subscript(indexPath: NSIndexPath) -> T { + CoreStore.assert( + !self.isPendingRefetch || NSThread.isMainThread(), + "Attempted to access a \(typeName(self)) outside the main thread while a refetch is in progress." + ) + return self.fetchedResultsController.objectAtIndexPath(indexPath) as! T } @@ -181,6 +191,11 @@ public final class ListMonitor { @warn_unused_result public func objectsInAllSections() -> [T] { + CoreStore.assert( + !self.isPendingRefetch || NSThread.isMainThread(), + "Attempted to access a \(typeName(self)) outside the main thread while a refetch is in progress." + ) + return (self.fetchedResultsController.fetchedObjects as? [T]) ?? [] } @@ -193,6 +208,11 @@ public final class ListMonitor { @warn_unused_result public func objectsInSection(section: Int) -> [T] { + CoreStore.assert( + !self.isPendingRefetch || NSThread.isMainThread(), + "Attempted to access a \(typeName(self)) outside the main thread while a refetch is in progress." + ) + return (self.fetchedResultsController.sections?[section].objects as? [T]) ?? [] } @@ -205,6 +225,11 @@ public final class ListMonitor { @warn_unused_result public func objectsInSection(safeSectionIndex section: Int) -> [T]? { + CoreStore.assert( + !self.isPendingRefetch || NSThread.isMainThread(), + "Attempted to access a \(typeName(self)) outside the main thread while a refetch is in progress." + ) + return (self.fetchedResultsController.sections?[section].objects as? [T]) ?? [] } @@ -216,6 +241,11 @@ public final class ListMonitor { @warn_unused_result public func numberOfSections() -> Int { + CoreStore.assert( + !self.isPendingRefetch || NSThread.isMainThread(), + "Attempted to access a \(typeName(self)) outside the main thread while a refetch is in progress." + ) + return self.fetchedResultsController.sections?.count ?? 0 } @@ -227,6 +257,11 @@ public final class ListMonitor { @warn_unused_result public func numberOfObjects() -> Int { + CoreStore.assert( + !self.isPendingRefetch || NSThread.isMainThread(), + "Attempted to access a \(typeName(self)) outside the main thread while a refetch is in progress." + ) + return self.fetchedResultsController.fetchedObjects?.count ?? 0 } @@ -263,6 +298,11 @@ public final class ListMonitor { @warn_unused_result public func sectionInfoAtIndex(section: Int) -> NSFetchedResultsSectionInfo { + CoreStore.assert( + !self.isPendingRefetch || NSThread.isMainThread(), + "Attempted to access a \(typeName(self)) outside the main thread while a refetch is in progress." + ) + return self.fetchedResultsController.sections![section] } @@ -275,6 +315,11 @@ public final class ListMonitor { @warn_unused_result public func sectionInfoAtIndex(safeSectionIndex section: Int) -> NSFetchedResultsSectionInfo? { + CoreStore.assert( + !self.isPendingRefetch || NSThread.isMainThread(), + "Attempted to access a \(typeName(self)) outside the main thread while a refetch is in progress." + ) + guard let sections = self.fetchedResultsController.sections where section < sections.count else { @@ -293,6 +338,11 @@ public final class ListMonitor { @warn_unused_result public func indexOf(object: T) -> Int? { + CoreStore.assert( + !self.isPendingRefetch || NSThread.isMainThread(), + "Attempted to access a \(typeName(self)) outside the main thread while a refetch is in progress." + ) + return (self.fetchedResultsController.fetchedObjects as? [T] ?? []).indexOf(object) } @@ -305,6 +355,11 @@ public final class ListMonitor { @warn_unused_result public func indexPathOf(object: T) -> NSIndexPath? { + CoreStore.assert( + !self.isPendingRefetch || NSThread.isMainThread(), + "Attempted to access a \(typeName(self)) outside the main thread while a refetch is in progress." + ) + return self.fetchedResultsController.indexPathForObject(object) } @@ -766,6 +821,11 @@ public final class ListMonitor { */ public func refetch(fetchClauses: [FetchClause]) { + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to refetch a \(typeName(self)) outside the main thread." + ) + self.isPendingRefetch = true NSNotificationCenter.defaultCenter().postNotificationName( @@ -780,19 +840,39 @@ public final class ListMonitor { return } + strongSelf.fetchedResultsControllerDelegate.fetchedResultsController = nil + let fetchRequest = strongSelf.fetchedResultsController.fetchRequest for clause in fetchClauses { clause.applyToFetchRequest(fetchRequest) } - try! strongSelf.fetchedResultsController.performFetch() - strongSelf.isPendingRefetch = false - - NSNotificationCenter.defaultCenter().postNotificationName( - ListMonitorDidRefetchListNotification, - object: strongSelf - ) + GCDQueue.Utility.async { + + guard let strongSelf = self else { + + return + } + + try! strongSelf.fetchedResultsController.performFetch() + + GCDQueue.Main.async { () -> Void in + + guard let strongSelf = self else { + + return + } + + strongSelf.fetchedResultsControllerDelegate.fetchedResultsController = strongSelf.fetchedResultsController + strongSelf.isPendingRefetch = false + + NSNotificationCenter.defaultCenter().postNotificationName( + ListMonitorDidRefetchListNotification, + object: strongSelf + ) + } + } } } @@ -808,6 +888,9 @@ public final class ListMonitor { fetchRequest.fetchLimit = 0 fetchRequest.resultType = .ManagedObjectResultType + fetchRequest.fetchBatchSize = 20 + fetchRequest.includesPendingChanges = false + fetchRequest.shouldRefreshRefetchedObjects = true for clause in fetchClauses { diff --git a/CoreStore/Observing/ObjectMonitor.swift b/CoreStore/Observing/ObjectMonitor.swift index abef246..de0fcb2 100644 --- a/CoreStore/Observing/ObjectMonitor.swift +++ b/CoreStore/Observing/ObjectMonitor.swift @@ -172,6 +172,8 @@ public final class ObjectMonitor { fetchRequest.fetchLimit = 0 fetchRequest.resultType = .ManagedObjectResultType fetchRequest.sortDescriptors = [] + fetchRequest.includesPendingChanges = false + fetchRequest.shouldRefreshRefetchedObjects = true let originalObjectID = object.objectID Where("SELF", isEqualTo: originalObjectID).applyToFetchRequest(fetchRequest)