Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions Code/CoreData/NSManagedObjectContext+RKAdditions.m
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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) {
Expand Down
9 changes: 6 additions & 3 deletions Code/CoreData/RKManagedObjectImporter.m
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}];

Expand Down
8 changes: 7 additions & 1 deletion Code/Network/RKManagedObjectRequestOperation.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
///----------------------------------------
Expand Down
75 changes: 44 additions & 31 deletions Code/Network/RKManagedObjectRequestOperation.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -794,14 +796,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;
}
}
}];

Expand Down Expand Up @@ -830,13 +836,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:^{
Expand Down
3 changes: 2 additions & 1 deletion Code/Network/RKObjectManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 17 additions & 2 deletions Code/Network/RKObjectManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down