From 4f35e4e3991133ccab588f9bb21feba0a6be97e2 Mon Sep 17 00:00:00 2001 From: Tony Arnold Date: Wed, 28 Nov 2012 08:12:51 +1100 Subject: [PATCH] Implement new save methods --- .../NSManagedObjectContext+MagicalSaves.h | 37 ++- .../NSManagedObjectContext+MagicalSaves.m | 219 ++++++++++++------ MagicalRecord/Core/MagicalRecord+Actions.h | 25 +- MagicalRecord/Core/MagicalRecord+Actions.m | 131 ++++++++--- 4 files changed, 288 insertions(+), 124 deletions(-) diff --git a/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.h b/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.h index 5c15ef6..4815c6c 100644 --- a/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.h +++ b/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.h @@ -8,17 +8,38 @@ #import +typedef NS_OPTIONS(NSUInteger, MRSaveContextOptions) { + MRSaveParentContexts = 1, + MRSaveSynchronously = 2 +}; + +typedef void (^MRSaveCompletionHandler)(BOOL success, NSError *error); + @interface NSManagedObjectContext (MagicalSaves) -- (void) MR_save; -- (void) MR_saveWithErrorCallback:(void(^)(NSError *))errorCallback; +// Asynchronous saving +- (void) MR_saveOnlySelfWithCompletion:(MRSaveCompletionHandler)completion; +- (void) MR_saveToPersistentStoreWithCompletion:(MRSaveCompletionHandler)completion; -- (void) MR_saveInBackgroundCompletion:(void (^)(void))completion; -- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback; -- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback completion:(void (^)(void))completion; +// Synchronous saving +- (void) MR_saveOnlySelfAndWait; +- (void) MR_saveToPersistentStoreAndWait; -- (void) MR_saveNestedContexts; -- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *))errorCallback; -- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *))errorCallback completion:(void (^)(void))completion; +// Save with options +- (void) MR_saveWithOptions:(MRSaveContextOptions)mask completion:(MRSaveCompletionHandler)completion; + +/* DEPRECATION NOTICE: + * The following methods are deprecated, but remain in place for backwards compatibility until the next major version (3.x) + */ +- (void) MR_save __attribute__((deprecated)); +- (void) MR_saveWithErrorCallback:(void(^)(NSError *))errorCallback __attribute__((deprecated)); + +- (void) MR_saveInBackgroundCompletion:(void (^)(void))completion __attribute__((deprecated)); +- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback __attribute__((deprecated)); +- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback completion:(void (^)(void))completion __attribute__((deprecated)); + +- (void) MR_saveNestedContexts __attribute__((deprecated)); +- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *))errorCallback __attribute__((deprecated)); +- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *))errorCallback completion:(void (^)(void))completion __attribute__((deprecated)); @end diff --git a/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.m b/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.m index aec15ad..62a6992 100644 --- a/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.m +++ b/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.m @@ -11,115 +11,180 @@ #import "NSManagedObjectContext+MagicalRecord.h" #import "MagicalRecord.h" -@interface NSManagedObjectContext (InternalMagicalSaves) - -- (void) MR_saveWithErrorCallback:(void(^)(NSError *))errorCallback; - -@end - - @implementation NSManagedObjectContext (MagicalSaves) -- (void) MR_saveWithErrorCallback:(void(^)(NSError *))errorCallback; +- (void)MR_saveOnlySelfWithCompletion:(MRSaveCompletionHandler)completion; { - if (![self hasChanges]) - { - MRLog(@"NO CHANGES IN CONTEXT %@ - NOT SAVING", [self MR_description]); + [self MR_saveWithOptions:0 completion:completion]; +} + +- (void)MR_saveOnlySelfAndWait; +{ + [self MR_saveWithOptions:MRSaveSynchronously completion:nil]; +} + +- (void) MR_saveToPersistentStoreWithCompletion:(MRSaveCompletionHandler)completion; +{ + [self MR_saveWithOptions:MRSaveParentContexts completion:completion]; +} + +- (void) MR_saveToPersistentStoreAndWait; +{ + [self MR_saveWithOptions:MRSaveParentContexts | MRSaveSynchronously completion:nil]; +} + +- (void)MR_saveWithOptions:(MRSaveContextOptions)mask completion:(MRSaveCompletionHandler)completion; +{ + BOOL syncSave = ((mask & MRSaveSynchronously) == MRSaveSynchronously); + BOOL saveParentContexts = ((mask & MRSaveParentContexts) == MRSaveParentContexts); + + if (![self hasChanges]) { + MRLog(@"NO CHANGES IN ** %@ ** CONTEXT - NOT SAVING", [self MR_workingName]); + + if (completion) + { + completion(NO, nil); + } + return; } - - MRLog(@"-> Saving %@", [self MR_description]); - - __block NSError *error = nil; - __block BOOL saved = NO; - @try - { - [self performBlockAndWait:^{ - saved = [self save:&error]; - }]; - } - @catch (NSException *exception) - { - MRLog(@"Unable to perform save: %@", (id)[exception userInfo] ?: (id)[exception reason]); - } - @finally - { - if (!saved) + + MRLog(@"→ Saving %@", [self MR_description]); + + id saveBlock = ^{ + NSError *error = nil; + BOOL saved = NO; + + @try { - if (errorCallback) - { - errorCallback(error); - } - else - { + saved = [self save:&error]; + } + @catch(NSException *exception) + { + MRLog(@"Unable to perform save: %@", (id)[exception userInfo] ? : (id)[exception reason]); + } + + @finally + { + if (!saved) { [MagicalRecord handleErrors:error]; + + if (completion) { + completion(saved, error); + } + } else { + // If we're the default context, save to disk too (the user expects it to persist) + if (self == [[self class] MR_defaultContext]) { + [[[self class] MR_rootSavingContext] MR_saveWithOptions:MRSaveSynchronously completion:completion]; + } + // If we're saving parent contexts, do so + else if ((YES == saveParentContexts) && [self parentContext]) { + [[self parentContext] MR_saveWithOptions:MRSaveSynchronously | MRSaveParentContexts completion:completion]; + } + // If we are not the default context (And therefore need to save the root context, do the completion action if one was specified + else { + MRLog(@"→ Finished saving: %@", [self MR_description]); + if (completion) { + completion(saved, error); + } + } } } + }; + + if (YES == syncSave) { + [self performBlockAndWait:saveBlock]; + } else { + [self performBlock:saveBlock]; } } -- (void) MR_saveNestedContexts; +#pragma mark - Deprecated methods +// These methods will be removed in MagicalRecord 3.0 + +- (void)MR_saveNestedContexts; { - [self MR_saveNestedContextsErrorHandler:nil]; + [self MR_saveToPersistentStoreWithCompletion:nil]; } -- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *))errorCallback; +- (void)MR_saveNestedContextsErrorHandler:(void (^)(NSError *))errorCallback; { - [self MR_saveNestedContextsErrorHandler:nil completion:nil]; + [self MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) { + if (!success) { + if (errorCallback) { + errorCallback(error); + } + } + }]; } -- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *))errorCallback completion:(void (^)(void))completion; +- (void)MR_saveNestedContextsErrorHandler:(void (^)(NSError *))errorCallback completion:(void (^)(void))completion; { - [self performBlock:^{ - [self MR_saveWithErrorCallback:errorCallback]; - if (self.parentContext) { - [[self parentContext] performBlock:^{ - [[self parentContext] MR_saveNestedContextsErrorHandler:errorCallback completion:completion]; - }]; - } else { + [self MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) { + if (success) { if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(); - }); + completion(); + } + } else { + if (errorCallback) { + errorCallback(error); } } }]; } -- (void) MR_save; +- (void)MR_save; { - [self MR_saveWithErrorCallback:nil]; + [self MR_saveToPersistentStoreAndWait]; } -- (void) MR_saveInBackgroundCompletion:(void (^)(void))completion; -{ - [self MR_saveInBackgroundErrorHandler:nil completion:completion]; -} +- (void)MR_saveWithErrorCallback:(void (^)(NSError *))errorCallback __attribute__((deprecated)); -- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback; { - [self MR_saveInBackgroundErrorHandler:errorCallback completion:nil]; -} - -- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback completion:(void (^)(void))completion; -{ - [self performBlock:^{ - // Save the context - [self MR_saveWithErrorCallback:errorCallback]; - - // If we're the default context, save to disk too (the user expects it to persist) - if (self == [[self class] MR_defaultContext]) - { - [[[self class] MR_rootSavingContext] MR_saveInBackgroundErrorHandler:errorCallback completion:completion]; - } - else - { - // If we are not the default context (And therefore need to save the root context, do the completion action if one was specified - if (completion) - { - dispatch_async(dispatch_get_main_queue(), completion); + [self MR_saveWithOptions:MRSaveSynchronously completion:^(BOOL success, NSError *error) { + if (!success) { + if (errorCallback) { + errorCallback(error); } } }]; } + +- (void)MR_saveInBackgroundCompletion:(void (^)(void))completion; +{ + [self MR_saveOnlySelfWithCompletion:^(BOOL success, NSError *error) { + if (success) { + if (completion) { + completion(); + } + } + }]; +} + +- (void)MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback; +{ + [self MR_saveOnlySelfWithCompletion:^(BOOL success, NSError *error) { + if (!success) { + if (errorCallback) { + errorCallback(error); + } + } + }]; +} + +- (void)MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback completion:(void (^)(void))completion; +{ + [self MR_saveOnlySelfWithCompletion:^(BOOL success, NSError *error) { + if (success) { + if (completion) { + completion(); + } + } else { + if (errorCallback) { + errorCallback(error); + } + } + }]; +} + @end diff --git a/MagicalRecord/Core/MagicalRecord+Actions.h b/MagicalRecord/Core/MagicalRecord+Actions.h index 4b2d60a..0bce647 100644 --- a/MagicalRecord/Core/MagicalRecord+Actions.h +++ b/MagicalRecord/Core/MagicalRecord+Actions.h @@ -7,21 +7,38 @@ #import #import "NSManagedObjectContext+MagicalRecord.h" +#import "NSManagedObjectContext+MagicalSaves.h" @interface MagicalRecord (Actions) -/* For saving on the current thread as the caller, only with a seperate context. Useful when you're managing your own threads/queues and need a serial call to create or change data +/* For all background saving operations. These calls will be sent to a different thread/queue. */ + (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block; ++ (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion; + +/* For saving on the current thread as the caller, only with a seperate context. Useful when you're managing your own threads/queues and need a serial call to create or change data + */ ++ (void) saveWithBlockAndWait:(void(^)(NSManagedObjectContext *localContext))block; + +/* + If you want to reuse the context on the current thread, use these methods. + */ ++ (void) saveUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion; ++ (void) saveUsingCurrentContextWithBlockAndWait:(void (^)(NSManagedObjectContext *localContext))block; + + +/* DEPRECATION NOTICE: + * The following methods are deprecated, but remain in place for backwards compatibility until the next major version (3.x) + */ /* For all background saving operations. These calls will be sent to a different thread/queue. */ -+ (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block; -+ (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(void(^)(void))callback; ++ (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block __attribute__((deprecated)); ++ (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(void(^)(void))completion __attribute__((deprecated)); /* If you want to reuse the context on the current thread, use this method. */ -+ (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *))errorHandler; ++ (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *))errorHandler __attribute__((deprecated)); @end diff --git a/MagicalRecord/Core/MagicalRecord+Actions.m b/MagicalRecord/Core/MagicalRecord+Actions.m index 2e56bbb..d886c27 100644 --- a/MagicalRecord/Core/MagicalRecord+Actions.m +++ b/MagicalRecord/Core/MagicalRecord+Actions.m @@ -8,63 +8,124 @@ #import "CoreData+MagicalRecord.h" #import "NSManagedObjectContext+MagicalRecord.h" + @implementation MagicalRecord (Actions) -+ (void) saveInBackgroundUsingContext:(NSManagedObjectContext *)localContext block:(void (^)(NSManagedObjectContext *))block completion:(void(^)(void))completion errorHandler:(void(^)(NSError *))errorHandler; +#pragma mark - Asynchronous saving + ++ (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block; { - [localContext performBlock: ^{ - block(localContext); - - [localContext MR_saveNestedContextsErrorHandler:errorHandler completion:completion]; + [self saveWithBlock:block completion:nil]; +} + ++ (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion; +{ + NSManagedObjectContext *mainContext = [NSManagedObjectContext MR_defaultContext]; + NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextWithParent:mainContext]; + + [localContext performBlock:^{ + if (block) { + block(localContext); + } + + [localContext MR_saveWithOptions:MRSaveParentContexts|MRSaveSynchronously completion:completion]; }]; } -+ (void) saveInBackgroundWithBlock:(void (^)(NSManagedObjectContext *))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *))errorHandler; -{ - NSManagedObjectContext *mainContext = [NSManagedObjectContext MR_defaultContext]; - NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextWithParent:mainContext]; - - [self saveInBackgroundUsingContext:localContext block:block completion:completion errorHandler:errorHandler]; -} - -+ (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *))errorHandler; ++ (void) saveUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion; { NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread]; - - [self saveInBackgroundUsingContext:localContext block:block completion:completion errorHandler:errorHandler]; + + [localContext performBlock:^{ + if (block) { + block(localContext); + } + + [localContext MR_saveWithOptions:MRSaveParentContexts|MRSaveSynchronously completion:completion]; + }]; } - -+ (void) saveWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *))errorHandler; + + +#pragma mark - Synchronous saving + ++ (void) saveWithBlockAndWait:(void(^)(NSManagedObjectContext *localContext))block; { NSManagedObjectContext *mainContext = [NSManagedObjectContext MR_defaultContext]; NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextWithParent:mainContext]; - block(localContext); - - if ([localContext hasChanges]) - { - [localContext MR_saveWithErrorCallback:errorHandler]; - } - - if (completion) - { - dispatch_async(dispatch_get_main_queue(), completion); - } + [localContext performBlockAndWait:^{ + if (block) { + block(localContext); + } + + [localContext MR_saveWithOptions:MRSaveParentContexts|MRSaveSynchronously completion:nil]; + }]; } -+ (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block -{ - [self saveWithBlock:block completion:nil errorHandler:nil]; ++ (void) saveUsingCurrentContextWithBlockAndWait:(void (^)(NSManagedObjectContext *localContext))block; +{ + NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread]; + + [localContext performBlockAndWait:^{ + if (block) { + block(localContext); + } + + [localContext MR_saveWithOptions:MRSaveParentContexts|MRSaveSynchronously completion:nil]; + }]; } + +#pragma mark - Deprecated methods + + (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block { - [self saveInBackgroundWithBlock:block completion:nil errorHandler:nil]; + [[self class] saveWithBlock:block completion:nil]; } -+ (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(void(^)(void))callback ++ (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(void(^)(void))completion { - [self saveInBackgroundWithBlock:block completion:callback errorHandler:nil]; + NSManagedObjectContext *mainContext = [NSManagedObjectContext MR_defaultContext]; + NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextWithParent:mainContext]; + + [localContext performBlock:^{ + if (block) + { + block(localContext); + } + + [localContext MR_saveToPersistentStoreAndWait]; + + if (completion) + { + completion(); + } + }]; +} + ++ (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *))errorHandler; +{ + NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread]; + + [localContext performBlock:^{ + if (block) { + block(localContext); + } + + [localContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) { + + if (success) { + if (completion) { + completion(); + } + } + else { + if (errorHandler) { + errorHandler(error); + } + } + }]; + }]; } @end