Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Suggested changes #204

Closed
wants to merge 6 commits into from

4 participants

@ChronicStim

While trying to incorporate MR into our existing app, I ran into a number of changes to the MR code that I think would be good to add to the base project. Here are 4 committed changes we are using that should help others using this library.

BTW, so far we've been pleased with the MR framework. We still don't have the full app migrated to the MR code, but what has been completed seems to be working well. I hope we can use this code to improve our overall Core Data speed and stability.

ChronicStim added some commits
@ChronicStim ChronicStim Existing methods did not provide an easy way to create a background s…
…ave operation in a context that was not a child of the MR_defaultContext. So, this new method takes any parent context as an input and then creates a child context on which to perform the background save.
777cbc3
@ChronicStim ChronicStim Change the && to || so that if the receiver is not the default or roo…
…tSaving contexts, the completion handler will still be run.

Also added a return; on the initial IF statement so that the completion handler does not get run twice as would be the case if the context was the MR_defaultContext.
9164aad
@ChronicStim ChronicStim Performing operations on objects within nested contexts where the MR_…
…inContext: method was used was throwing errors when objects with tempID's were being passed to the method. The solution was to check if the ID is temporary and then if it is, request a permanentID before continuing.
80774c0
@ChronicStim ChronicStim Adding an easy to understand identification string to contexts throug…
…h the userInfo property on the context. This is then referenced when using the MR_description method or can be used independently in your own MRLog debug comments. Quite helpful when you're dealing with multiple nested contexts. For example, I might name a context "DiaryEntryEditingContext" or "DiaryEntryDeletionContext" using these methods.

The MR_description method was also updated to show a recursive "family tree" of the contexts by looking for the parentContext, then the parent of the parent, etc. and listing them in the string.
7d6ef7a
@ChronicStim

Oopps! The existing line wasn't updated to use the new variable "objectID". It should also be updated to look like:

NSManagedObject *inContext = [otherContext existingObjectWithID:objectID error:&error];

The code still works even without the change since the [self objectID] will now pull a perm id, but for accuracy it should be changed. Thanks.

ChronicStim added some commits
@ChronicStim ChronicStim Correcting line where we didn't replace [self objectID] with objectID. 3ac593b
@ChronicStim ChronicStim Switched background save method's action from performBlockAndWait bac…
…k to performBlock to try and reduce blocking the main thread. This was giving problems early in the integration of MR, but it seems to be working fine now, so I believe it should be defined this way.
c9c64b8
@itavor

+1 for merging these... the temporary objectID patch fixed several problems in my MagicalRecord-using app at once. Haven't tested the other patches but they look promising with regards to several other issues I'm having with MagicalRecord.

@blackgold9
Collaborator

I like this change in principle, and have been experimenting with background saves going direct to the root saving context (not through the default context). The trick is, we need to keep the library simple, and adding yet another save method isn't achieving that. It may be that background saves all need to go directly to the root context (big ui perf gains there), but i haven't worked out all the details on making sure the UI is kept up to date in that case. Thoughts?

I do agree with the idea that you don't want to burden the framework with unnecessary methods, but in this case I would suggest making an exception. The reason I needed this method will likely be encountered by others using the framework in their apps. Let me explain my situation and you can be the judge of whether or not it makes sense to add.

Our app is a chronic pain tracking application - basically a diary where users create daily entries that record pain levels, location, descriptions, etc. In the simplest case, consider that we have a base level MOC (defaultContext) that holds the existing diary entries. Now the user begins to create a new entry. We create this in a different MOC and only merge it to the base MOC when the user saves the entry (if they cancel the entry, we just dump the MOC and the base MOC remains unaffected). Okay, so no big deal.

But now consider that during the course of creating the diary entry there are some steps where the user does something that requires a process with lots of data changes/generation. In our case, we offer a tool where the user paints the location of their pain on a diagram. That process is actually handled within another MOC because we don't know if the user is going to save the painted diagram or not (this is a separate decision from whether or not the overall diary entry is saved).

Prior to finding MagicalRecord, we had developed our own method of observation/notification between unrelated MOCs to deal with this sort of workflow. However, the usage of the Parent/Child concept makes sense. In this case we would have the default MOC (A) as the base level parent container, then a child MOC (B) for the generation of the diary entry, and then a child MOC (C) for the diagram painting operation. So MOC C is child to MOC B and grandchild of MOC A. This gives us the ability to save the data changes from MOC C which updates MOC B, but not MOC A. So, if the user later cancels the overall diary entry process and we drop MOC B, we still have a clean base MOC A.

In order to make this work and to provide us with the option of doing some of these processes in the background, we needed to create the method in question because we wanted to create a child of MOC B which would not impact MOC A when it was saved.

Hopefully that described our thought process clearly enough. I can say that the new method has worked out very well and we use it in a number of locations within the application.

@tonyarnold
Owner

I believe a lot of these changes have been pulled in in different ways over the last year, mostly inspired by this PR. Thank you for taking the time to contribute your changes!

@tonyarnold tonyarnold closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 9, 2012
  1. @ChronicStim

    Existing methods did not provide an easy way to create a background s…

    ChronicStim authored
    …ave operation in a context that was not a child of the MR_defaultContext. So, this new method takes any parent context as an input and then creates a child context on which to perform the background save.
  2. @ChronicStim

    Change the && to || so that if the receiver is not the default or roo…

    ChronicStim authored
    …tSaving contexts, the completion handler will still be run.
    
    Also added a return; on the initial IF statement so that the completion handler does not get run twice as would be the case if the context was the MR_defaultContext.
  3. @ChronicStim

    Performing operations on objects within nested contexts where the MR_…

    ChronicStim authored
    …inContext: method was used was throwing errors when objects with tempID's were being passed to the method. The solution was to check if the ID is temporary and then if it is, request a permanentID before continuing.
  4. @ChronicStim

    Adding an easy to understand identification string to contexts throug…

    ChronicStim authored
    …h the userInfo property on the context. This is then referenced when using the MR_description method or can be used independently in your own MRLog debug comments. Quite helpful when you're dealing with multiple nested contexts. For example, I might name a context "DiaryEntryEditingContext" or "DiaryEntryDeletionContext" using these methods.
    
    The MR_description method was also updated to show a recursive "family tree" of the contexts by looking for the parentContext, then the parent of the parent, etc. and listing them in the string.
  5. @ChronicStim
Commits on Jul 13, 2012
  1. @ChronicStim

    Switched background save method's action from performBlockAndWait bac…

    ChronicStim authored
    …k to performBlock to try and reduce blocking the main thread. This was giving problems early in the integration of MR, but it seems to be working fine now, so I believe it should be defined this way.
This page is out of date. Refresh to see the latest.
View
13 MagicalRecord/Categories/NSManagedObject/NSManagedObject+MagicalRecord.m
@@ -221,8 +221,19 @@ + (BOOL) MR_truncateAll
- (id) MR_inContext:(NSManagedObjectContext *)otherContext
{
+ NSManagedObjectID *objectID = [self objectID];
+ if ([objectID isTemporaryID]) {
+ MRLog(@"Object (%@) has temporaryID. Attempting to generate permanentID.",[[self class] description]);
+ NSError *error = nil;
+ if ([[self managedObjectContext] obtainPermanentIDsForObjects:[NSArray arrayWithObject:self] error:&error]) {
+ objectID = [self objectID];
+ MRLog(@"Object (%@) was granted a permanentID.",[[self class] description]);
+ } else {
+ [MagicalRecord handleErrors:error];
+ }
+ }
NSError *error = nil;
- NSManagedObject *inContext = [otherContext existingObjectWithID:[self objectID] error:&error];
+ NSManagedObject *inContext = [otherContext existingObjectWithID:objectID error:&error];
[MagicalRecord handleErrors:error];
return inContext;
View
3  MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalRecord.h
@@ -25,4 +25,7 @@ extern NSString * const kMagicalRecordDidMergeChangesFromiCloudNotification;
- (NSString *) MR_description;
+- (void) MR_setContextWorkingName:(NSString *)workingName;
+- (NSString *) MR_contextWorkingName;
+
@end
View
28 MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalRecord.m
@@ -11,6 +11,7 @@
static NSManagedObjectContext *rootSavingContext = nil;
static NSManagedObjectContext *defaultManagedObjectContext_ = nil;
+#define kNSManagedObjectContextWorkingName @"kNSManagedObjectContextWorkingName"
@interface NSManagedObjectContext (MagicalRecordInternal)
@@ -35,9 +36,16 @@ - (NSString *) MR_description;
NSString *contextName = (self == defaultManagedObjectContext_) ? @"*** DEFAULT ***" : @"";
contextName = (self == rootSavingContext) ? @"*** BACKGROUND SAVE ***" : contextName;
- NSString *onMainThread = [NSThread isMainThread] ? @"*** MAIN THREAD ***" : @"";
+ NSString *onMainThread = [NSThread isMainThread] ? @"*** MAIN THREAD ***" : @"*** SECONDARY THREAD ***";
- return [NSString stringWithFormat:@"%@: %@ Context %@", [self description], contextName, onMainThread];
+ NSString *familyTree = [NSString string];
+ NSManagedObjectContext *parentContext = [self parentContext];
+ while (nil != parentContext) {
+ familyTree = [familyTree stringByAppendingFormat:@" ==> %@;",[parentContext MR_contextWorkingName]];
+ parentContext = [parentContext parentContext];
+ }
+
+ return [NSString stringWithFormat:@"%@: %@ Context %@ \nFamilyTree: %@", [self MR_contextWorkingName], contextName, onMainThread,familyTree];
}
+ (NSManagedObjectContext *) MR_defaultContext
@@ -74,6 +82,7 @@ + (void) MR_setRootSavingContext:(NSManagedObjectContext *)context;
{
rootSavingContext = context;
[rootSavingContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
+ [rootSavingContext MR_setContextWorkingName:@"rootSavingsContext"];
}
+ (void) MR_initializeDefaultContextWithCoordinator:(NSPersistentStoreCoordinator *)coordinator;
@@ -85,6 +94,7 @@ + (void) MR_initializeDefaultContextWithCoordinator:(NSPersistentStoreCoordinato
[self MR_setRootSavingContext:rootContext];
NSManagedObjectContext *defaultContext = [self MR_newMainQueueContext];
+ [defaultContext MR_setContextWorkingName:@"defaultContext"];
[defaultContext setParentContext:rootSavingContext];
[self MR_setDefaultContext:defaultContext];
@@ -149,5 +159,19 @@ + (NSManagedObjectContext *) MR_contextWithStoreCoordinator:(NSPersistentStoreCo
return context;
}
+- (void) MR_setContextWorkingName:(NSString *)workingName;
+{
+ [[self userInfo] setObject:workingName forKey:kNSManagedObjectContextWorkingName];
+}
+
+- (NSString *) MR_contextWorkingName;
+{
+ NSString *workingName = [[self userInfo] objectForKey:kNSManagedObjectContextWorkingName];
+ if (nil == workingName) {
+ workingName = @"UndefinedWorkingContext";
+ }
+ return workingName;
+}
+
@end
View
5 MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.m
@@ -98,15 +98,16 @@ - (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback;
- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback completion:(void (^)(void))completion;
{
- [self performBlockAndWait:^{
+ [self performBlock:^{
[self MR_saveWithErrorCallback:errorCallback];
if (self == [[self class] MR_defaultContext])
{
[[[self class] MR_rootSavingContext] MR_saveInBackgroundErrorHandler:errorCallback completion:completion];
+ return;
}
- if (completion && self == [[self class] MR_rootSavingContext])
+ if (completion || self == [[self class] MR_rootSavingContext])
{
dispatch_async(dispatch_get_main_queue(), completion);
}
View
2  MagicalRecord/Core/MagicalRecord+Actions.h
@@ -25,4 +25,6 @@
*/
+ (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *))errorHandler;
++ (void) saveInBackgroundUsingChildOfParentContext:(NSManagedObjectContext *)parentContext WithBlock:(void (^)(NSManagedObjectContext *))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *))errorHandler;
+
@end
View
7 MagicalRecord/Core/MagicalRecord+Actions.m
@@ -57,6 +57,13 @@ + (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectC
[self saveInBackgroundUsingContext:localContext block:block completion:completion errorHandler:errorHandler];
}
++ (void) saveInBackgroundUsingChildOfParentContext:(NSManagedObjectContext *)parentContext WithBlock:(void (^)(NSManagedObjectContext *))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *))errorHandler;
+{
+ NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextWithParent:parentContext];
+
+ [self saveInBackgroundUsingContext:localContext block:block completion:completion errorHandler:errorHandler];
+}
+
+ (void) saveWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *))errorHandler;
{
NSManagedObjectContext *mainContext = [NSManagedObjectContext MR_defaultContext];
Something went wrong with that request. Please try again.