From bbdf42d0fd3531ad94ea6db19557b45cd339915c Mon Sep 17 00:00:00 2001 From: Luniz Date: Wed, 27 Sep 2023 20:41:49 +0600 Subject: [PATCH 1/2] Do not save contextx if there are no any changes. --- .../NSManagedObjectContext+RKAdditions.m | 19 +++++++--- Code/CoreData/RKManagedObjectImporter.m | 9 +++-- .../Network/RKManagedObjectRequestOperation.m | 37 ++++++++++++------- 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/Code/CoreData/NSManagedObjectContext+RKAdditions.m b/Code/CoreData/NSManagedObjectContext+RKAdditions.m index 8f22e10c19..d0ce7da1f6 100644 --- a/Code/CoreData/NSManagedObjectContext+RKAdditions.m +++ b/Code/CoreData/NSManagedObjectContext+RKAdditions.m @@ -41,8 +41,6 @@ - (BOOL)saveToPersistentStore:(NSError **)error __block NSError *localError = nil; NSManagedObjectContext *contextToSave = self; while (contextToSave) { - __block BOOL success; - /** To work around issues in ios 5 first obtain permanent object ids for any inserted objects. If we don't do this then its easy to get an `NSObjectInaccessibleException`. This happens when: @@ -52,18 +50,27 @@ - (BOOL)saveToPersistentStore:(NSError **)error 4. Save the child context to the parent context (the main one) which will work, 5. Save the main context - a NSObjectInaccessibleException will occur and Core Data will either crash your app or lock it up (a semaphore is not correctly released on the first error so the next fetch request will block forever. */ - __block BOOL obtained; + __block BOOL obtained = YES; [contextToSave performBlockAndWait:^{ - obtained = [contextToSave obtainPermanentIDsForObjects:[[contextToSave insertedObjects] allObjects] error:&localError]; + NSArray *objects = [[contextToSave insertedObjects] allObjects]; + if (objects.count > 0) { + obtained = + [contextToSave obtainPermanentIDsForObjects:objects error:&localError]; + } }]; if (!obtained) { if (error) *error = localError; return NO; } + __block BOOL success = YES; [contextToSave performBlockAndWait:^{ - success = [contextToSave save:&localError]; - if (! success && localError == nil) RKLogWarning(@"Saving of managed object context failed, but a `nil` value for the `error` argument was returned. This typically indicates an invalid implementation of a key-value validation method exists within your model. This violation of the API contract may result in the save operation being mis-interpretted by callers that rely on the availability of the error."); + if ([contextToSave hasChanges]) { + success = [contextToSave save:&localError]; + if (! success && localError == nil) { + RKLogWarning(@"Saving of managed object context failed, but a `nil` value for the `error` argument was returned. This typically indicates an invalid implementation of a key-value validation method exists within your model. This violation of the API contract may result in the save operation being mis-interpretted by callers that rely on the availability of the error."); + } + } }]; if (! success) { diff --git a/Code/CoreData/RKManagedObjectImporter.m b/Code/CoreData/RKManagedObjectImporter.m index 65213b7b51..e7d38d0f62 100644 --- a/Code/CoreData/RKManagedObjectImporter.m +++ b/Code/CoreData/RKManagedObjectImporter.m @@ -275,10 +275,13 @@ - (BOOL)finishImporting:(NSError **)error __block BOOL success; __block NSError *localError = nil; + __weak typeof (self) weakSelf = self; [self.managedObjectContext performBlockAndWait:^{ - success = [self.managedObjectContext save:&localError]; - if (! success) { - RKLogCoreDataError(localError); + if ([weakSelf.managedObjectContext hasChanges]) { + success = [weakSelf.managedObjectContext save:&localError]; + if (! success) { + RKLogCoreDataError(localError); + } } }]; diff --git a/Code/Network/RKManagedObjectRequestOperation.m b/Code/Network/RKManagedObjectRequestOperation.m index 0525ae0c03..9988d2b5c2 100644 --- a/Code/Network/RKManagedObjectRequestOperation.m +++ b/Code/Network/RKManagedObjectRequestOperation.m @@ -794,14 +794,18 @@ - (BOOL)saveContextToPersistentStore:(NSManagedObjectContext *)contextToSave fai { __block NSError *localError = nil; while (contextToSave) { - __block BOOL success; + __block BOOL success = YES; [contextToSave performBlockAndWait:^{ - if (! [self isCancelled]) { - success = [contextToSave save:&localError]; - if (! success && localError == nil) RKLogWarning(@"Saving of managed object context failed, but a `nil` value for the `error` argument was returned. This typically indicates an invalid implementation of a key-value validation method exists within your model. This violation of the API contract may result in the save operation being mis-interpretted by callers that rely on the availability of the error."); - } else { - // We have been cancelled while the save is in progress -- bail - success = NO; + if ([contextToSave hasChanges]) { + if (! [self isCancelled]) { + success = [contextToSave save:&localError]; + if (! success && localError == nil) { + RKLogWarning(@"Saving of managed object context failed, but a `nil` value for the `error` argument was returned. This typically indicates an invalid implementation of a key-value validation method exists within your model. This violation of the API contract may result in the save operation being mis-interpretted by callers that rely on the availability of the error."); + } + } else { + // We have been cancelled while the save is in progress -- bail + success = NO; + } } }]; @@ -830,13 +834,20 @@ - (BOOL)saveContext:(NSManagedObjectContext *)context error:(NSError **)error if (self.savesToPersistentStore) { success = [self saveContextToPersistentStore:context failedContext:&failedContext error:&localError]; } else { - [context performBlockAndWait:^{ - success = ([self isCancelled]) ? NO : [context save:&localError]; - if (!success) { - failedContext = context; - } - }]; + if ([self isCancelled]) { + success = NO; + } else { + [context performBlockAndWait:^{ + if ([context hasChanges]) { + success = [context save:&localError]; + if (!success) { + failedContext = context; + } + } + }]; + } } + if (success) { if ([self.targetObject isKindOfClass:[NSManagedObject class]]) { [self.managedObjectContext performBlock:^{ From bd5137f235b02afa148371bfd041791da57c59c8 Mon Sep 17 00:00:00 2001 From: Luniz Date: Tue, 20 Aug 2024 17:05:38 +0600 Subject: [PATCH 2/2] Add the ability to avoid saving the CoreData context before an API call. --- .../Network/RKManagedObjectRequestOperation.h | 8 +++- .../Network/RKManagedObjectRequestOperation.m | 38 ++++++++++--------- Code/Network/RKObjectManager.h | 3 +- Code/Network/RKObjectManager.m | 19 +++++++++- 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/Code/Network/RKManagedObjectRequestOperation.h b/Code/Network/RKManagedObjectRequestOperation.h index c76886ee71..c55b1b2929 100644 --- a/Code/Network/RKManagedObjectRequestOperation.h +++ b/Code/Network/RKManagedObjectRequestOperation.h @@ -121,7 +121,13 @@ @see `RKManagedObjectResponseMapperOperation` */ @interface RKManagedObjectRequestOperation : RKObjectRequestOperation - +/** + If `true`, the CoreData context will be saved before making an API call. + + Disabling context saving can help avoid potential app freezes, as the context is + saved synchronously on the thread from which it's called, including the main thread. + */ +@property (nonatomic, assign) BOOL shouldSaveContextBeforeAPICall; ///---------------------------------------- /// @name Configuring Core Data Integration ///---------------------------------------- diff --git a/Code/Network/RKManagedObjectRequestOperation.m b/Code/Network/RKManagedObjectRequestOperation.m index 9988d2b5c2..aa99ae3c9e 100644 --- a/Code/Network/RKManagedObjectRequestOperation.m +++ b/Code/Network/RKManagedObjectRequestOperation.m @@ -473,6 +473,7 @@ - (instancetype)initWithHTTPRequestOperation:(RKHTTPRequestOperation *)requestOp self = [super initWithHTTPRequestOperation:requestOperation responseDescriptors:responseDescriptors]; if (self) { self.savesToPersistentStore = YES; + self.shouldSaveContextBeforeAPICall = YES; self.deletesOrphanedObjects = YES; self.cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:requestOperation.request]; } @@ -513,28 +514,29 @@ - (void)setManagedObjectContext:(NSManagedObjectContext *)managedObjectContext _managedObjectContext = managedObjectContext; if (managedObjectContext) { - [managedObjectContext performBlockAndWait:^{ - if ([managedObjectContext hasChanges]) { - if ([managedObjectContext.insertedObjects count] && [self.managedObjectCache respondsToSelector:@selector(didCreateObject:)]) { - for (NSManagedObject *managedObject in managedObjectContext.insertedObjects) { - [self.managedObjectCache didCreateObject:managedObject]; + if (self.shouldSaveContextBeforeAPICall) { + [managedObjectContext performBlockAndWait:^{ + if ([managedObjectContext hasChanges]) { + if ([managedObjectContext.insertedObjects count] && [self.managedObjectCache respondsToSelector:@selector(didCreateObject:)]) { + for (NSManagedObject *managedObject in managedObjectContext.insertedObjects) { + [self.managedObjectCache didCreateObject:managedObject]; + } } - } - - if ([managedObjectContext.updatedObjects count] && [self.managedObjectCache respondsToSelector:@selector(didFetchObject:)]) { - for (NSManagedObject *managedObject in managedObjectContext.updatedObjects) { - [self.managedObjectCache didFetchObject:managedObject]; + + if ([managedObjectContext.updatedObjects count] && [self.managedObjectCache respondsToSelector:@selector(didFetchObject:)]) { + for (NSManagedObject *managedObject in managedObjectContext.updatedObjects) { + [self.managedObjectCache didFetchObject:managedObject]; + } } - } - - if ([managedObjectContext.deletedObjects count] && [self.managedObjectCache respondsToSelector:@selector(didDeleteObject:)]) { - for (NSManagedObject *managedObject in managedObjectContext.deletedObjects) { - [self.managedObjectCache didDeleteObject:managedObject]; + + if ([managedObjectContext.deletedObjects count] && [self.managedObjectCache respondsToSelector:@selector(didDeleteObject:)]) { + for (NSManagedObject *managedObject in managedObjectContext.deletedObjects) { + [self.managedObjectCache didDeleteObject:managedObject]; + } } } - } - }]; - + }]; + } // Create a private context NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [privateContext setParentContext:managedObjectContext]; diff --git a/Code/Network/RKObjectManager.h b/Code/Network/RKObjectManager.h index de3ddad654..470b09193e 100644 --- a/Code/Network/RKObjectManager.h +++ b/Code/Network/RKObjectManager.h @@ -484,7 +484,8 @@ RKMappingResult, RKRequestDescriptor, RKResponseDescriptor; */ #ifdef RKCoreDataIncluded - (RKManagedObjectRequestOperation *)managedObjectRequestOperationWithRequest:(NSURLRequest *)request - managedObjectContext:(NSManagedObjectContext *)managedObjectContext + shouldSaveContextBeforeAPICall:(BOOL)shouldSave + managedObjectContext:(NSManagedObjectContext *)managedObjectContext success:(void (^)(RKObjectRequestOperation *operation, RKMappingResult *mappingResult))success failure:(void (^)(RKObjectRequestOperation *operation, NSError *error))failure; #endif diff --git a/Code/Network/RKObjectManager.m b/Code/Network/RKObjectManager.m index 693edb0744..06217fd8f9 100644 --- a/Code/Network/RKObjectManager.m +++ b/Code/Network/RKObjectManager.m @@ -600,15 +600,22 @@ - (RKObjectRequestOperation *)objectRequestOperationWithRequest:(NSURLRequest *) #ifdef RKCoreDataIncluded - (RKManagedObjectRequestOperation *)managedObjectRequestOperationWithRequest:(NSURLRequest *)request + shouldSaveContextBeforeAPICall:(BOOL)shouldSave managedObjectContext:(NSManagedObjectContext *)managedObjectContext success:(void (^)(RKObjectRequestOperation *operation, RKMappingResult *mappingResult))success failure:(void (^)(RKObjectRequestOperation *operation, NSError *error))failure { - return [self managedObjectRequestOperationWithRequest:request responseDescriptors:self.responseDescriptors managedObjectContext:managedObjectContext success:success failure:failure]; + return [self managedObjectRequestOperationWithRequest:request + responseDescriptors:self.responseDescriptors + shouldSaveContextBeforeAPICall:shouldSave + managedObjectContext:managedObjectContext + success:success + failure:failure]; } - (RKManagedObjectRequestOperation *)managedObjectRequestOperationWithRequest:(NSURLRequest *)request responseDescriptors:(NSArray *)responseDescriptors + shouldSaveContextBeforeAPICall:(BOOL)shouldSave managedObjectContext:(NSManagedObjectContext *)managedObjectContext success:(void (^)(RKObjectRequestOperation *operation, RKMappingResult *mappingResult))success failure:(void (^)(RKObjectRequestOperation *operation, NSError *error))failure @@ -619,6 +626,9 @@ - (RKManagedObjectRequestOperation *)managedObjectRequestOperationWithRequest:(N Class objectRequestOperationClass = [self requestOperationClassForRequest:request fromRegisteredClasses:self.registeredManagedObjectRequestOperationClasses] ?: [RKManagedObjectRequestOperation class]; RKManagedObjectRequestOperation *operation = (RKManagedObjectRequestOperation *)[[objectRequestOperationClass alloc] initWithHTTPRequestOperation:HTTPRequestOperation responseDescriptors:responseDescriptors]; [operation setCompletionBlockWithSuccess:success failure:failure]; + + operation.shouldSaveContextBeforeAPICall = shouldSave; + operation.managedObjectContext = managedObjectContext ?: self.managedObjectStore.mainQueueManagedObjectContext; operation.managedObjectCache = self.managedObjectStore.managedObjectCache; operation.fetchRequestBlocks = self.fetchRequestBlocks; @@ -659,7 +669,12 @@ - (id)appropriateObjectRequestOperationWithObject:(id)object if (isManagedObjectRequestOperation && self.managedObjectStore) { // Construct a Core Data operation NSManagedObjectContext *managedObjectContext = [object respondsToSelector:@selector(managedObjectContext)] ? [object managedObjectContext] : self.managedObjectStore.mainQueueManagedObjectContext; - operation = [self managedObjectRequestOperationWithRequest:request responseDescriptors:matchingDescriptors managedObjectContext:managedObjectContext success:nil failure:nil]; + operation = [self managedObjectRequestOperationWithRequest:request + responseDescriptors:matchingDescriptors + shouldSaveContextBeforeAPICall:YES + managedObjectContext:managedObjectContext + success:nil + failure:nil]; if ([object isKindOfClass:[NSManagedObject class]]) { static NSPredicate *temporaryObjectsPredicate = nil;