From ed8c7b35e8c32131dc9ddb4cfa62c33be441b91e Mon Sep 17 00:00:00 2001 From: John Estropia Date: Tue, 27 Sep 2016 17:31:08 +0900 Subject: [PATCH] Reduce leaking (a little) on the workaround for NSFetchRequest.affectedStores ARC bug --- CoreStoreTests/FromTests.swift | 120 +++++++++++++-------------- CoreStoreTests/GroupByTests.swift | 2 +- CoreStoreTests/OrderByTests.swift | 2 +- CoreStoreTests/TweakTests.swift | 2 +- CoreStoreTests/WhereTests.swift | 2 +- Sources/ObjectiveC/CoreStoreBridge.h | 3 + Sources/ObjectiveC/CoreStoreBridge.m | 25 +++++- Sources/Observing/ListMonitor.swift | 4 +- 8 files changed, 91 insertions(+), 69 deletions(-) diff --git a/CoreStoreTests/FromTests.swift b/CoreStoreTests/FromTests.swift index 48665ce..5c03671 100644 --- a/CoreStoreTests/FromTests.swift +++ b/CoreStoreTests/FromTests.swift @@ -74,33 +74,33 @@ final class FromTests: BaseTestCase { let from = From() - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext) XCTAssertTrue(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertEqual(affectedConfigurations, ["PF_DEFAULT_CONFIGURATION_NAME"]) } do { let from = From("Config1") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = self.expectLogger([.LogWarning]) { from.applyToFetchRequest(request, context: dataStack.mainContext) } XCTAssertFalse(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertTrue(affectedConfigurations.isEmpty) } } @@ -115,102 +115,102 @@ final class FromTests: BaseTestCase { let from = From() - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext) XCTAssertTrue(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertEqual(affectedConfigurations, ["Config1"]) } do { let from = From("Config1") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext) XCTAssertTrue(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertEqual(affectedConfigurations, ["Config1"]) } do { let from = From("Config2") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = self.expectLogger([.LogWarning]) { from.applyToFetchRequest(request, context: dataStack.mainContext) } XCTAssertFalse(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertTrue(affectedConfigurations.isEmpty) } do { let from = From() - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = self.expectLogger([.LogWarning]) { from.applyToFetchRequest(request, context: dataStack.mainContext) } XCTAssertFalse(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertTrue(affectedConfigurations.isEmpty) } do { let from = From("Config1") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = self.expectLogger([.LogWarning]) { from.applyToFetchRequest(request, context: dataStack.mainContext) } XCTAssertFalse(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertTrue(affectedConfigurations.isEmpty) } do { let from = From("Config2") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = self.expectLogger([.LogWarning]) { from.applyToFetchRequest(request, context: dataStack.mainContext) } XCTAssertFalse(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertTrue(affectedConfigurations.isEmpty) } } @@ -225,99 +225,99 @@ final class FromTests: BaseTestCase { let from = From() - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext) XCTAssertTrue(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertEqual(Set(affectedConfigurations), ["PF_DEFAULT_CONFIGURATION_NAME", "Config1"] as Set) } do { let from = From("Config1") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext) XCTAssertTrue(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertEqual(affectedConfigurations, ["Config1"]) } do { let from = From("Config2") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = self.expectLogger([.LogWarning]) { from.applyToFetchRequest(request, context: dataStack.mainContext) } XCTAssertFalse(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertTrue(affectedConfigurations.isEmpty) } do { let from = From() - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext) XCTAssertTrue(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertEqual(affectedConfigurations, ["PF_DEFAULT_CONFIGURATION_NAME"]) } do { let from = From("Config1") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = self.expectLogger([.LogWarning]) { from.applyToFetchRequest(request, context: dataStack.mainContext) } XCTAssertFalse(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertTrue(affectedConfigurations.isEmpty) } do { let from = From("Config2") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = self.expectLogger([.LogWarning]) { from.applyToFetchRequest(request, context: dataStack.mainContext) } XCTAssertFalse(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertTrue(affectedConfigurations.isEmpty) } } @@ -332,96 +332,96 @@ final class FromTests: BaseTestCase { let from = From() - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext) XCTAssertTrue(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertEqual(affectedConfigurations, ["Config1"]) } do { let from = From("Config1") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext) XCTAssertTrue(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertEqual(affectedConfigurations, ["Config1"]) } do { let from = From("Config2") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = self.expectLogger([.LogWarning]) { from.applyToFetchRequest(request, context: dataStack.mainContext) } XCTAssertFalse(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertTrue(affectedConfigurations.isEmpty) } do { let from = From() - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext) XCTAssertTrue(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertEqual(affectedConfigurations, ["Config2"]) } do { let from = From("Config1") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = self.expectLogger([.LogWarning]) { from.applyToFetchRequest(request, context: dataStack.mainContext) } XCTAssertFalse(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertTrue(affectedConfigurations.isEmpty) } do { let from = From("Config2") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext) XCTAssertTrue(storesFound) XCTAssertNotNil(request.entity) - XCTAssertNotNil(request.affectedStores) + XCTAssertNotNil(request.safeAffectedStores) XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName)) - let affectedConfigurations = request.affectedStores!.map { $0.configurationName } + let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName } XCTAssertEqual(affectedConfigurations, ["Config2"]) } } diff --git a/CoreStoreTests/GroupByTests.swift b/CoreStoreTests/GroupByTests.swift index 5ed3aa2..3bc8fd7 100644 --- a/CoreStoreTests/GroupByTests.swift +++ b/CoreStoreTests/GroupByTests.swift @@ -68,7 +68,7 @@ final class GroupByTests: BaseTestCase { let groupBy = GroupBy("testString") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() _ = From(TestEntity1).applyToFetchRequest(request, context: dataStack.mainContext) groupBy.applyToFetchRequest(request) diff --git a/CoreStoreTests/OrderByTests.swift b/CoreStoreTests/OrderByTests.swift index 483f508..2eea837 100644 --- a/CoreStoreTests/OrderByTests.swift +++ b/CoreStoreTests/OrderByTests.swift @@ -179,7 +179,7 @@ final class OrderByTests: XCTestCase { dynamic func test_ThatOrderByClauses_ApplyToFetchRequestsCorrectly() { let orderBy = OrderBy(.Ascending("key")) - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() orderBy.applyToFetchRequest(request) XCTAssertNotNil(request.sortDescriptors) XCTAssertEqual(request.sortDescriptors ?? [], orderBy.sortDescriptors) diff --git a/CoreStoreTests/TweakTests.swift b/CoreStoreTests/TweakTests.swift index 18416d4..d8f1aae 100644 --- a/CoreStoreTests/TweakTests.swift +++ b/CoreStoreTests/TweakTests.swift @@ -43,7 +43,7 @@ final class TweakTests: XCTestCase { $0.fetchLimit = 200 $0.predicate = predicate } - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() tweak.applyToFetchRequest(request) XCTAssertEqual(request.fetchOffset, 100) XCTAssertEqual(request.fetchLimit, 200) diff --git a/CoreStoreTests/WhereTests.swift b/CoreStoreTests/WhereTests.swift index 053e657..24044e5 100644 --- a/CoreStoreTests/WhereTests.swift +++ b/CoreStoreTests/WhereTests.swift @@ -142,7 +142,7 @@ final class WhereTests: XCTestCase { dynamic func test_ThatWhereClauses_ApplyToFetchRequestsCorrectly() { let whereClause = Where("key", isEqualTo: "value") - let request = NSFetchRequest() + let request = CoreStoreFetchRequest() whereClause.applyToFetchRequest(request) XCTAssertNotNil(request.predicate) XCTAssertEqual(request.predicate, whereClause.predicate) diff --git a/Sources/ObjectiveC/CoreStoreBridge.h b/Sources/ObjectiveC/CoreStoreBridge.h index 7809fd1..e685223 100644 --- a/Sources/ObjectiveC/CoreStoreBridge.h +++ b/Sources/ObjectiveC/CoreStoreBridge.h @@ -576,6 +576,9 @@ CSWhere *_Nonnull CSWherePredicate(NSPredicate *_Nonnull predicate) CORESTORE_RE // http://stackoverflow.com/questions/14396375/nsfetchedresultscontroller-crashes-in-ios-6-if-affectedstores-is-specified NS_SWIFT_NAME(CoreStoreFetchRequest) @interface _CSFetchRequest: NSFetchRequest + +@property (nullable, nonatomic, copy, readonly) NSArray *safeAffectedStores; + @end diff --git a/Sources/ObjectiveC/CoreStoreBridge.m b/Sources/ObjectiveC/CoreStoreBridge.m index 2a7039d..2ded9b0 100644 --- a/Sources/ObjectiveC/CoreStoreBridge.m +++ b/Sources/ObjectiveC/CoreStoreBridge.m @@ -223,16 +223,35 @@ CSWhere *_Nonnull CSWherePredicate(NSPredicate *_Nonnull predicate) CORESTORE_RE #pragma mark CoreStoreFetchRequest @interface _CSFetchRequest () + +@property (nullable, nonatomic, copy) NSArray *safeAffectedStores; +@property (nullable, nonatomic, assign) CFArrayRef releaseArray; + @end @implementation _CSFetchRequest -- (NSArray *)affectedStores { +// MARK: NSFetchRequest + +- (void)setAffectedStores:(NSArray *_Nullable)affectedStores { // Bugfix for NSFetchRequest messing up memory management for `affectedStores` // http://stackoverflow.com/questions/14396375/nsfetchedresultscontroller-crashes-in-ios-6-if-affectedstores-is-specified - CFBridgingRetain([super affectedStores]); - return [super affectedStores]; + + if (NSFoundationVersionNumber < NSFoundationVersionNumber10_0) { + + self.safeAffectedStores = affectedStores; + [super setAffectedStores:affectedStores]; + return; + } + if (self.releaseArray != NULL) { + + CFRelease(self.releaseArray); + self.releaseArray = NULL; + } + self.safeAffectedStores = affectedStores; + [super setAffectedStores:affectedStores]; + self.releaseArray = CFBridgingRetain([super affectedStores]); } @end diff --git a/Sources/Observing/ListMonitor.swift b/Sources/Observing/ListMonitor.swift index e4742ad..24cc7fd 100644 --- a/Sources/Observing/ListMonitor.swift +++ b/Sources/Observing/ListMonitor.swift @@ -1123,7 +1123,7 @@ public final class ListMonitor: Hashable { self.isPersistentStoreChanging = true guard let removedStores = (note.userInfo?[NSRemovedPersistentStoresKey] as? [NSPersistentStore]).flatMap(Set.init) - where !Set(self.fetchedResultsController.fetchRequest.affectedStores ?? []).intersect(removedStores).isEmpty else { + where !Set((self.fetchedResultsController.fetchRequest as! CoreStoreFetchRequest).safeAffectedStores ?? []).intersect(removedStores).isEmpty else { return } @@ -1144,7 +1144,7 @@ public final class ListMonitor: Hashable { if !self.isPendingRefetch { - let previousStores = Set(self.fetchedResultsController.fetchRequest.affectedStores ?? []) + let previousStores = Set((self.fetchedResultsController.fetchRequest as! CoreStoreFetchRequest).safeAffectedStores ?? []) let currentStores = previousStores .subtract(note.userInfo?[NSRemovedPersistentStoresKey] as? [NSPersistentStore] ?? []) .union(note.userInfo?[NSAddedPersistentStoresKey] as? [NSPersistentStore] ?? [])