From 8e53af7fd60217c6164cd3fa1047e80651fcdd99 Mon Sep 17 00:00:00 2001 From: Adam Price Date: Wed, 24 Oct 2012 14:58:47 -0400 Subject: [PATCH 1/3] Added AFFetchSaveManager to listen for notifications and trigger completion blocks --- AFIncrementalStore/AFFetchSaveManager.h | 49 +++++++ AFIncrementalStore/AFFetchSaveManager.m | 163 ++++++++++++++++++++++++ AFIncrementalStore/AFIncrementalStore.h | 57 +++++++++ AFIncrementalStore/AFIncrementalStore.m | 58 ++++++++- 4 files changed, 321 insertions(+), 6 deletions(-) create mode 100644 AFIncrementalStore/AFFetchSaveManager.h create mode 100644 AFIncrementalStore/AFFetchSaveManager.m diff --git a/AFIncrementalStore/AFFetchSaveManager.h b/AFIncrementalStore/AFFetchSaveManager.h new file mode 100644 index 0000000..1d66226 --- /dev/null +++ b/AFIncrementalStore/AFFetchSaveManager.h @@ -0,0 +1,49 @@ +// +// AFFetchSaveManager.h +// Sharely +// +// Created by Adam Price on 10/26/12. +// Copyright (c) 2012 Fuzz Productions. All rights reserved. +// + +@class AFHTTPRequestOperation; + +typedef void (^AFIncrementalStoreFetchCompletionBlock)(NSFetchRequest *fetchRequest, AFHTTPRequestOperation *operation, NSArray *fetchedObjectIDs); +typedef void (^AFIncrementalStoreSaveCompletionBlock)(NSSaveChangesRequest *saveChangesRequest, AFHTTPRequestOperation *operation, NSArray *insertedObjectIDs, NSArray *updatedObjectIDs, NSArray *deletedObjectIDs); + +@interface AFFetchSaveManager : NSObject + +/** + `AFFetchSaveManager` is a class designed to manage executing NSFetchRequests with completion blocks and NSManagedObjectContext saves with completion blocks. There is no need to create an instance of this class; the class itself is the observer for notifications and the only public methods are class methods. + + ## Description + + There are only two class methods with which to interface with AFFetchSaveManager. An exception will be raised if you call these methods without a context. + + The completion and failure blocks will be associated with the corresponding NSFetchRequest or NSSaveChangesRequest that are executed by AFIncrementalStore. + AFFetchSaveManager acts as a global observer of AFIncrementalStore's NSNotifications, and will execute the appropriate completion block when it gets the corresponding + didFetchRemoteValues: or didSaveRemoteValues: notification. + */ + ++ (BOOL)saveContext:(NSManagedObjectContext *)context + error:(NSError *__autoreleasing*)error + completion:(AFIncrementalStoreSaveCompletionBlock)completionBlock; + ++ (NSArray *)executeFetchRequest:(NSFetchRequest *)fetchRequest + context:(NSManagedObjectContext *)context + error:(NSError *__autoreleasing*)error + completion:(AFIncrementalStoreFetchCompletionBlock)completionBlock; + +///----------------------------------------- +/// @name ManagedObjectContext userInfo keys +///----------------------------------------- + +/** + A key in the `userInfo` dictionary of a NSManagedObjectContext, set by AFFetchSaveManager. + The corresponding value is a unique `requestIdentifier` key that is generated in each of AFFetchSaveManager's class methods and used as a key for the pending completion block + of that NSPersistentStoreRequest. This key is immediately removed from the `userInfo` dictionary and attached to the appropriate NSFetchRequest or NSSaveChangesRequest in the + executeRequest:withContext:error method of AFIncrementalStore. + */ +extern NSString * const AFFetchSaveManagerPersistentStoreRequestIdentifierKey; + +@end diff --git a/AFIncrementalStore/AFFetchSaveManager.m b/AFIncrementalStore/AFFetchSaveManager.m new file mode 100644 index 0000000..958cb43 --- /dev/null +++ b/AFIncrementalStore/AFFetchSaveManager.m @@ -0,0 +1,163 @@ +// +// AFFetchSaveManager.m +// Sharely +// +// Created by Adam Price on 10/26/12. +// Copyright (c) 2012 Fuzz Productions. All rights reserved. +// + +#import +#import "AFFetchSaveManager.h" +#import "AFIncrementalStore.h" + +NSString * const AFFetchSaveManagerPersistentStoreRequestIdentifierKey = @"AFFetchSaveManagerPersistentStoreRequestIdentifierKey"; + +static NSMutableDictionary *_fetchRequestBlockDictionary = nil; +static NSMutableDictionary *_saveRequestBlockDictionary = nil; + +@implementation AFFetchSaveManager + ++ (void)setupObserver +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken,^ + { + [[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(willFetchRemoteValues:) name:AFIncrementalStoreContextWillFetchRemoteValues object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(didFetchRemoteValues:) name:AFIncrementalStoreContextDidFetchRemoteValues object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(willSaveRemoteValues:) name:AFIncrementalStoreContextWillSaveRemoteValues object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(didSaveRemoteValues:) name:AFIncrementalStoreContextDidSaveRemoteValues object:nil]; + }); +} + +#pragma mark - +#pragma mark NSManagedObjectContext public class methods + ++ (NSArray *)executeFetchRequest:(NSFetchRequest *)fetchRequest + context:(NSManagedObjectContext *)context + error:(NSError *__autoreleasing*)error + completion:(AFIncrementalStoreFetchCompletionBlock)completionBlock +{ + NSParameterAssert(context); + + [[self class] setupObserver]; + + if (completionBlock) { + NSString *requestIdentifier = [[NSProcessInfo processInfo] globallyUniqueString]; + + [context.userInfo setObject:requestIdentifier forKey:AFFetchSaveManagerPersistentStoreRequestIdentifierKey]; + + [[[self class] fetchRequestBlockDictionary] setObject:completionBlock forKey:requestIdentifier]; + } + + return [context executeFetchRequest:fetchRequest error:error]; +} + ++ (BOOL)saveContext:(NSManagedObjectContext *)context + error:(NSError *__autoreleasing*)error + completion:(AFIncrementalStoreSaveCompletionBlock)completionBlock +{ + NSParameterAssert(context); + + [[self class] setupObserver]; + + if (completionBlock) { + NSString *requestIdentifier = [[NSProcessInfo processInfo] globallyUniqueString]; + + [context.userInfo setObject:requestIdentifier forKey:AFFetchSaveManagerPersistentStoreRequestIdentifierKey]; + + [[[self class] saveRequestBlockDictionary] setObject:completionBlock forKey:requestIdentifier]; + } + + return [context save:error]; +} + +#pragma mark - +#pragma mark Private Getter Methods + ++ (NSMutableDictionary *)fetchRequestBlockDictionary +{ + if (!_fetchRequestBlockDictionary) + _fetchRequestBlockDictionary = [[NSMutableDictionary alloc] init]; + return _fetchRequestBlockDictionary; +} + ++ (NSMutableDictionary *)saveRequestBlockDictionary +{ + if (!_saveRequestBlockDictionary) + _saveRequestBlockDictionary = [[NSMutableDictionary alloc] init]; + return _saveRequestBlockDictionary; +} + +#pragma mark - +#pragma mark NSNotifications + ++ (void)willFetchRemoteValues:(NSNotification *)inNotification +{ + +} + ++ (void)didFetchRemoteValues:(NSNotification *)inNotification +{ + [[self class] executeCompletionBlockWithNotification:inNotification]; +} + ++ (void)willSaveRemoteValues:(NSNotification *)inNotification +{ + +} + ++ (void)didSaveRemoteValues:(NSNotification *)inNotification +{ + [[self class] executeCompletionBlockWithNotification:inNotification]; +} + +#pragma mark - +#pragma mark Private block handler functions + ++ (void)executeCompletionBlockWithNotification:(NSNotification *)inNotification +{ + BOOL couldNotFindValidCompletionBlock = NO; + + NSPersistentStoreRequest *tmpPersistentStoreRequest = [inNotification.userInfo objectForKey:AFIncrementalStorePersistentStoreRequestKey]; + AFHTTPRequestOperation *tmpOperation = [inNotification.userInfo objectForKey:AFIncrementalStoreRequestOperationKey]; + if (tmpPersistentStoreRequest && tmpOperation) + { + NSString *requestIdentifier = [tmpPersistentStoreRequest af_requestIdentifier]; + if (!requestIdentifier || requestIdentifier.length == 0) + return; + + if (tmpPersistentStoreRequest.requestType == NSFetchRequestType) + { + AFIncrementalStoreFetchCompletionBlock tmpFetchCompletionBlock = [[[self class] fetchRequestBlockDictionary] objectForKey:requestIdentifier]; + if (!tmpFetchCompletionBlock) + couldNotFindValidCompletionBlock = YES; + else + { + NSArray *tmpFetchedObjects = [inNotification.userInfo objectForKey:AFIncrementalStoreFetchedObjectIDsKey]; + if (tmpFetchCompletionBlock) + tmpFetchCompletionBlock((NSFetchRequest *)tmpPersistentStoreRequest, tmpOperation, tmpFetchedObjects); + } + } + else + { + AFIncrementalStoreSaveCompletionBlock tmpSaveCompletionBlock = [[[self class] saveRequestBlockDictionary] objectForKey:requestIdentifier]; + if (!tmpSaveCompletionBlock) + couldNotFindValidCompletionBlock = YES; + else + { + NSArray *tmpInsertedObjects = [inNotification.userInfo objectForKey:AFIncrementalStoreInsertedObjectIDsKey]; + NSArray *tmpUpdatedObjects = [inNotification.userInfo objectForKey:AFIncrementalStoreUpdatedObjectIDsKey]; + NSArray *tmpDeletedObjects = [inNotification.userInfo objectForKey:AFIncrementalStoreDeletedObjectIDsKey]; + if (tmpSaveCompletionBlock) + tmpSaveCompletionBlock((NSSaveChangesRequest *)tmpPersistentStoreRequest, tmpOperation, tmpInsertedObjects, tmpUpdatedObjects, tmpDeletedObjects); + } + } + } + else + couldNotFindValidCompletionBlock = YES; + + if (couldNotFindValidCompletionBlock) + NSLog(@"Could Not Find Valid Completion Block"); +} + +@end diff --git a/AFIncrementalStore/AFIncrementalStore.h b/AFIncrementalStore/AFIncrementalStore.h index 457a0c9..5883075 100644 --- a/AFIncrementalStore/AFIncrementalStore.h +++ b/AFIncrementalStore/AFIncrementalStore.h @@ -23,6 +23,34 @@ #import #import "AFNetworking.h" +#import + +@interface NSPersistentStoreRequest (_AFIncrementalStore) + +/** + A property set by AFIncrementalStore to uniquely identify a NSFetchRequest or NSSaveChanges request, in order for the AFFetchSaveManager class to correctly identify it via NSNotification, and subsequently fire the appropriate completion block for that request. + + @discussion If there is a completion block associated with the NSPersistentStoreRequest, AFFetchSaveManager will have generated this unique identifier upon first dispatch of the request and conveys it to the AFIncrementalStore by attaching it to the `userInfo` dictionary of the NSManagedObjectContext. + */ +@property (readwrite, nonatomic, copy, setter = af_setRequestIdentifier:) NSString *af_requestIdentifier; + +@end + +static char kAFRequestIdentifierObjectKey; + +@implementation NSPersistentStoreRequest (_AFIncrementalStore) +@dynamic af_requestIdentifier; + +- (NSString *)af_requestIdentifier { + return (NSString *)objc_getAssociatedObject(self, &kAFRequestIdentifierObjectKey); +} + +- (void)af_setRequestIdentifier:(NSString *)requestIdentifier { + objc_setAssociatedObject(self, &kAFRequestIdentifierObjectKey, requestIdentifier, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + +@end + @protocol AFIncrementalStoreDelegate; @protocol AFIncrementalStoreHTTPClient; @@ -317,4 +345,33 @@ extern NSString * const AFIncrementalStoreRequestOperationKey; /** A key in the `userInfo` dictionary in a `AFIncrementalStoreContextWillFetchRemoteValues` or `AFIncrementalStoreContextDidFetchRemoteValues` notification. The corresponding value is an `NSPersistentStoreRequest` object representing the associated fetch or save request. */ + extern NSString * const AFIncrementalStorePersistentStoreRequestKey; + +/** + A key in the `userInfo` dictionary in a `AFIncrementalStoreContextWillFetchRemoteValues` or `AFIncrementalStoreContextDidFetchRemoteValues` notification. + The corresponding value is a NSArray containing the permament object ID's associated with the fetched objects from the originating managed object context. The object ID's are included in the NSNotification because unlike the NSManagedObjects themselves, the IDs are thread-safe. + */ + +extern NSString * const AFIncrementalStoreFetchedObjectIDsKey; + +/** + A key in the `userInfo` dictionary in a `AFIncrementalStoreContextWillFetchRemoteValues` or `AFIncrementalStoreContextDidFetchRemoteValues` notification. + The corresponding value is a NSArray containing the permament object ID's associated with the inserted objects from the originating managed object context. The object ID's are included in the NSNotification because unlike the NSManagedObjects themselves, the IDs are thread-safe. + */ + +extern NSString * const AFIncrementalStoreInsertedObjectIDsKey; + +/** + A key in the `userInfo` dictionary in a `AFIncrementalStoreContextWillFetchRemoteValues` or `AFIncrementalStoreContextDidFetchRemoteValues` notification. + The corresponding value is a NSArray containing the permament object ID's associated with the updated objects from the originating managed object context. The object ID's are included in the NSNotification because unlike the NSManagedObjects themselves, the IDs are thread-safe. + */ + +extern NSString * const AFIncrementalStoreUpdatedObjectIDsKey; + +/** + A key in the `userInfo` dictionary in a `AFIncrementalStoreContextWillFetchRemoteValues` or `AFIncrementalStoreContextDidFetchRemoteValues` notification. + The corresponding value is a NSArray containing the permament object ID's associated with the deleted objects from the originating managed object context. The object ID's are included in the NSNotification because unlike the NSManagedObjects themselves, the IDs are thread-safe. + */ + +extern NSString * const AFIncrementalStoreDeletedObjectIDsKey; diff --git a/AFIncrementalStore/AFIncrementalStore.m b/AFIncrementalStore/AFIncrementalStore.m index 475d524..14b8752 100644 --- a/AFIncrementalStore/AFIncrementalStore.m +++ b/AFIncrementalStore/AFIncrementalStore.m @@ -23,8 +23,10 @@ #import "AFIncrementalStore.h" #import "AFHTTPClient.h" #import "ISO8601DateFormatter.h" +#import "AFFetchSaveManager.h" #import + NSString * const AFIncrementalStoreUnimplementedMethodException = @"com.alamofire.incremental-store.exceptions.unimplemented-method"; NSString * const AFIncrementalStoreRelationshipCardinalityException = @"com.alamofire.incremental-store.exceptions.relationship-cardinality"; @@ -34,6 +36,10 @@ NSString * const AFIncrementalStoreContextDidSaveRemoteValues = @"AFIncrementalStoreContextDidSaveRemoteValues"; NSString * const AFIncrementalStoreRequestOperationKey = @"AFIncrementalStoreRequestOperation"; NSString * const AFIncrementalStorePersistentStoreRequestKey = @"AFIncrementalStorePersistentStoreRequest"; +NSString * const AFIncrementalStoreFetchedObjectIDsKey = @"AFIncrementalStoreFetchedObjectsKey"; +NSString * const AFIncrementalStoreInsertedObjectIDsKey = @"AFIncrementalStoreInsertedObjectIDsKey"; +NSString * const AFIncrementalStoreUpdatedObjectIDsKey = @"AFIncrementalStoreUpdatedObjectIDsKey"; +NSString * const AFIncrementalStoreDeletedObjectIDsKey = @"AFIncrementalStoreDeletedObjectIDsKey"; static NSString * const kAFIncrementalStoreResourceIdentifierAttributeName = @"__af_resourceIdentifier"; static NSString * const kAFIncrementalStoreLastModifiedAttributeName = @"__af_lastModified"; @@ -108,12 +114,15 @@ + (NSManagedObjectModel *)model { - (void)notifyManagedObjectContext:(NSManagedObjectContext *)context aboutRequestOperation:(AFHTTPRequestOperation *)operation forFetchRequest:(NSFetchRequest *)fetchRequest + withFetchedObjectIDs:(NSArray *)fetchedObjectIDs { NSString *notificationName = [operation isFinished] ? AFIncrementalStoreContextDidFetchRemoteValues : AFIncrementalStoreContextWillFetchRemoteValues; NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; [userInfo setObject:operation forKey:AFIncrementalStoreRequestOperationKey]; [userInfo setObject:fetchRequest forKey:AFIncrementalStorePersistentStoreRequestKey]; + if ([operation isFinished] && fetchedObjectIDs) + [userInfo setObject:fetchedObjectIDs forKey:AFIncrementalStoreFetchedObjectIDsKey]; [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:context userInfo:userInfo]; } @@ -121,12 +130,21 @@ - (void)notifyManagedObjectContext:(NSManagedObjectContext *)context - (void)notifyManagedObjectContext:(NSManagedObjectContext *)context aboutRequestOperations:(NSArray *)operations forSaveChangesRequest:(NSSaveChangesRequest *)saveChangesRequest + withInsertedObjectIDs:(NSArray *)insertedObjectIDs + withUpdatedObjectIDs:(NSArray *)updatedObjectIDs + withDeletedObjectIDs:(NSArray *)deletedObjectIDs { NSString *notificationName = [[operations lastObject] isFinished] ? AFIncrementalStoreContextDidSaveRemoteValues : AFIncrementalStoreContextWillSaveRemoteValues; NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; [userInfo setObject:operations forKey:AFIncrementalStoreRequestOperationKey]; [userInfo setObject:saveChangesRequest forKey:AFIncrementalStorePersistentStoreRequestKey]; + if (insertedObjectIDs) + [userInfo setObject:insertedObjectIDs forKey:AFIncrementalStoreInsertedObjectIDsKey]; + if (updatedObjectIDs) + [userInfo setObject:updatedObjectIDs forKey:AFIncrementalStoreUpdatedObjectIDsKey]; + if (deletedObjectIDs) + [userInfo setObject:deletedObjectIDs forKey:AFIncrementalStoreDeletedObjectIDsKey]; [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:context userInfo:userInfo]; } @@ -282,6 +300,13 @@ - (id)executeRequest:(NSPersistentStoreRequest *)persistentStoreRequest withContext:(NSManagedObjectContext *)context error:(NSError *__autoreleasing *)error { + NSString *requestIdentifier = [context.userInfo objectForKey:AFFetchSaveManagerPersistentStoreRequestIdentifierKey]; + if (requestIdentifier && requestIdentifier.length) + { + [persistentStoreRequest af_setRequestIdentifier:requestIdentifier]; + [context.userInfo removeObjectForKey:AFFetchSaveManagerPersistentStoreRequestIdentifierKey]; + } + if (persistentStoreRequest.requestType == NSFetchRequestType) { return [self executeFetchRequest:(NSFetchRequest *)persistentStoreRequest withContext:context error:error]; } else if (persistentStoreRequest.requestType == NSSaveRequestType) { @@ -315,18 +340,22 @@ - (id)executeFetchRequest:(NSFetchRequest *)fetchRequest if (![[self backingManagedObjectContext] save:error] || ![childContext save:error]) { NSLog(@"Error: %@", *error); } + + NSMutableArray *tmpObjectIDs = [NSMutableArray array]; + for (NSManagedObject *object in managedObjects) + [tmpObjectIDs addObject:object.objectID]; + + [self notifyManagedObjectContext:context aboutRequestOperation:operation forFetchRequest:fetchRequest withFetchedObjectIDs:tmpObjectIDs]; }]; - - [self notifyManagedObjectContext:context aboutRequestOperation:operation forFetchRequest:fetchRequest]; }]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"Error: %@", error); - [self notifyManagedObjectContext:context aboutRequestOperation:operation forFetchRequest:fetchRequest]; + [self notifyManagedObjectContext:context aboutRequestOperation:operation forFetchRequest:fetchRequest withFetchedObjectIDs:nil]; }]; operation.successCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); - [self notifyManagedObjectContext:context aboutRequestOperation:operation forFetchRequest:fetchRequest]; + [self notifyManagedObjectContext:context aboutRequestOperation:operation forFetchRequest:fetchRequest withFetchedObjectIDs:nil]; [self.HTTPClient enqueueHTTPRequestOperation:operation]; } @@ -377,7 +406,9 @@ - (id)executeSaveChangesRequest:(NSSaveChangesRequest *)saveChangesRequest NSMutableArray *mutableOperations = [NSMutableArray array]; NSManagedObjectContext *backingContext = [self backingManagedObjectContext]; + __block NSMutableArray *insertedObjectIDs; if ([self.HTTPClient respondsToSelector:@selector(requestForInsertedObject:)]) { + insertedObjectIDs = [[NSMutableArray alloc] initWithCapacity:[[saveChangesRequest insertedObjects] count]]; for (NSManagedObject *insertedObject in [saveChangesRequest insertedObjects]) { NSURLRequest *request = [self.HTTPClient requestForInsertedObject:insertedObject]; AFHTTPRequestOperation *operation = [self.HTTPClient HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { @@ -394,6 +425,7 @@ - (id)executeSaveChangesRequest:(NSSaveChangesRequest *)saveChangesRequest }]; [context obtainPermanentIDsForObjects:[NSArray arrayWithObject:insertedObject] error:nil]; + [insertedObjectIDs addObject:[insertedObject objectID]]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"Insert Error: %@", error); }]; @@ -402,7 +434,9 @@ - (id)executeSaveChangesRequest:(NSSaveChangesRequest *)saveChangesRequest } } + __block NSMutableArray *updatedObjectIDs; if ([self.HTTPClient respondsToSelector:@selector(requestForUpdatedObject:)]) { + updatedObjectIDs = [[NSMutableArray alloc] initWithCapacity:[[saveChangesRequest updatedObjects] count]]; for (NSManagedObject *updatedObject in [saveChangesRequest updatedObjects]) { NSURLRequest *request = [self.HTTPClient requestForUpdatedObject:updatedObject]; AFHTTPRequestOperation *operation = [self.HTTPClient HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { @@ -421,10 +455,14 @@ - (id)executeSaveChangesRequest:(NSSaveChangesRequest *)saveChangesRequest } } + __block NSMutableArray *deletedObjectIDs; if ([self.HTTPClient respondsToSelector:@selector(requestForDeletedObject:)]) { + deletedObjectIDs = [[NSMutableArray alloc] initWithCapacity:[[saveChangesRequest deletedObjects] count]]; for (NSManagedObject *deletedObject in [saveChangesRequest deletedObjects]) { NSURLRequest *request = [self.HTTPClient requestForDeletedObject:deletedObject]; AFHTTPRequestOperation *operation = [self.HTTPClient HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { + + [deletedObjectIDs addObject:deletedObject.objectID]; [backingContext performBlockAndWait:^{ NSManagedObject *backingObject = [backingContext existingObjectWithID:deletedObject.objectID error:nil]; [backingContext deleteObject:backingObject]; @@ -438,10 +476,18 @@ - (id)executeSaveChangesRequest:(NSSaveChangesRequest *)saveChangesRequest } } - [self notifyManagedObjectContext:context aboutRequestOperations:mutableOperations forSaveChangesRequest:saveChangesRequest]; + [self notifyManagedObjectContext:context aboutRequestOperations:mutableOperations forSaveChangesRequest:saveChangesRequest + withInsertedObjectIDs:nil + withUpdatedObjectIDs:nil + withDeletedObjectIDs:nil]; [self.HTTPClient enqueueBatchOfHTTPRequestOperations:mutableOperations progressBlock:nil completionBlock:^(NSArray *operations) { - [self notifyManagedObjectContext:context aboutRequestOperations:operations forSaveChangesRequest:saveChangesRequest]; + [self notifyManagedObjectContext:context + aboutRequestOperations:operations + forSaveChangesRequest:saveChangesRequest + withInsertedObjectIDs:insertedObjectIDs + withUpdatedObjectIDs:updatedObjectIDs + withDeletedObjectIDs:deletedObjectIDs]; }]; return [NSArray array]; From f9c2f5a8bf3a545b67d8c966ea31065822edc5fa Mon Sep 17 00:00:00 2001 From: Adam Price Date: Wed, 31 Oct 2012 12:40:42 -0400 Subject: [PATCH 2/3] Updated NSNotifications to take NSManagedObjects in the calling context, and deletedObjectIDs (since the objects do not exist anymore) --- AFIncrementalStore/AFFetchSaveManager.m | 6 +-- AFIncrementalStore/AFIncrementalStore.h | 20 +++---- AFIncrementalStore/AFIncrementalStore.m | 69 +++++++++++++++---------- 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/AFIncrementalStore/AFFetchSaveManager.m b/AFIncrementalStore/AFFetchSaveManager.m index 958cb43..225926b 100644 --- a/AFIncrementalStore/AFFetchSaveManager.m +++ b/AFIncrementalStore/AFFetchSaveManager.m @@ -133,7 +133,7 @@ + (void)executeCompletionBlockWithNotification:(NSNotification *)inNotification couldNotFindValidCompletionBlock = YES; else { - NSArray *tmpFetchedObjects = [inNotification.userInfo objectForKey:AFIncrementalStoreFetchedObjectIDsKey]; + NSArray *tmpFetchedObjects = [inNotification.userInfo objectForKey:AFIncrementalStoreFetchedObjectsKey]; if (tmpFetchCompletionBlock) tmpFetchCompletionBlock((NSFetchRequest *)tmpPersistentStoreRequest, tmpOperation, tmpFetchedObjects); } @@ -145,8 +145,8 @@ + (void)executeCompletionBlockWithNotification:(NSNotification *)inNotification couldNotFindValidCompletionBlock = YES; else { - NSArray *tmpInsertedObjects = [inNotification.userInfo objectForKey:AFIncrementalStoreInsertedObjectIDsKey]; - NSArray *tmpUpdatedObjects = [inNotification.userInfo objectForKey:AFIncrementalStoreUpdatedObjectIDsKey]; + NSArray *tmpInsertedObjects = [inNotification.userInfo objectForKey:AFIncrementalStoreInsertedObjectsKey]; + NSArray *tmpUpdatedObjects = [inNotification.userInfo objectForKey:AFIncrementalStoreUpdatedObjectsKey]; NSArray *tmpDeletedObjects = [inNotification.userInfo objectForKey:AFIncrementalStoreDeletedObjectIDsKey]; if (tmpSaveCompletionBlock) tmpSaveCompletionBlock((NSSaveChangesRequest *)tmpPersistentStoreRequest, tmpOperation, tmpInsertedObjects, tmpUpdatedObjects, tmpDeletedObjects); diff --git a/AFIncrementalStore/AFIncrementalStore.h b/AFIncrementalStore/AFIncrementalStore.h index 5883075..876a670 100644 --- a/AFIncrementalStore/AFIncrementalStore.h +++ b/AFIncrementalStore/AFIncrementalStore.h @@ -349,28 +349,28 @@ extern NSString * const AFIncrementalStoreRequestOperationKey; extern NSString * const AFIncrementalStorePersistentStoreRequestKey; /** - A key in the `userInfo` dictionary in a `AFIncrementalStoreContextWillFetchRemoteValues` or `AFIncrementalStoreContextDidFetchRemoteValues` notification. - The corresponding value is a NSArray containing the permament object ID's associated with the fetched objects from the originating managed object context. The object ID's are included in the NSNotification because unlike the NSManagedObjects themselves, the IDs are thread-safe. + A key in the `userInfo` dictionary in a `AFIncrementalStoreContextDidFetchRemoteValues` notification. + The corresponding value is an `NSArray` object containing the fetched objects, in the context in which they were requested. */ -extern NSString * const AFIncrementalStoreFetchedObjectIDsKey; +extern NSString * const AFIncrementalStoreFetchedObjectsKey; /** - A key in the `userInfo` dictionary in a `AFIncrementalStoreContextWillFetchRemoteValues` or `AFIncrementalStoreContextDidFetchRemoteValues` notification. - The corresponding value is a NSArray containing the permament object ID's associated with the inserted objects from the originating managed object context. The object ID's are included in the NSNotification because unlike the NSManagedObjects themselves, the IDs are thread-safe. + A key in the `userInfo` dictionary in a `AFIncrementalStoreContextDidSaveRemoteValues` notification. + The corresponding value is an `NSArray` object containing the inserted objects, in the context in which they were requested. */ -extern NSString * const AFIncrementalStoreInsertedObjectIDsKey; +extern NSString * const AFIncrementalStoreInsertedObjectsKey; /** - A key in the `userInfo` dictionary in a `AFIncrementalStoreContextWillFetchRemoteValues` or `AFIncrementalStoreContextDidFetchRemoteValues` notification. - The corresponding value is a NSArray containing the permament object ID's associated with the updated objects from the originating managed object context. The object ID's are included in the NSNotification because unlike the NSManagedObjects themselves, the IDs are thread-safe. + A key in the `userInfo` dictionary in a `AFIncrementalStoreContextDidSaveRemoteValues` notification. + The corresponding value is an `NSArray` object containing the updated objects, in the context in which they were requested. */ -extern NSString * const AFIncrementalStoreUpdatedObjectIDsKey; +extern NSString * const AFIncrementalStoreUpdatedObjectsKey; /** - A key in the `userInfo` dictionary in a `AFIncrementalStoreContextWillFetchRemoteValues` or `AFIncrementalStoreContextDidFetchRemoteValues` notification. + A key in the `userInfo` dictionary in a `AFIncrementalStoreContextWillSaveRemoteValues` or `AFIncrementalStoreContextDidFetchRemoteValues` notification. The corresponding value is a NSArray containing the permament object ID's associated with the deleted objects from the originating managed object context. The object ID's are included in the NSNotification because unlike the NSManagedObjects themselves, the IDs are thread-safe. */ diff --git a/AFIncrementalStore/AFIncrementalStore.m b/AFIncrementalStore/AFIncrementalStore.m index 14b8752..014b7af 100644 --- a/AFIncrementalStore/AFIncrementalStore.m +++ b/AFIncrementalStore/AFIncrementalStore.m @@ -36,9 +36,9 @@ NSString * const AFIncrementalStoreContextDidSaveRemoteValues = @"AFIncrementalStoreContextDidSaveRemoteValues"; NSString * const AFIncrementalStoreRequestOperationKey = @"AFIncrementalStoreRequestOperation"; NSString * const AFIncrementalStorePersistentStoreRequestKey = @"AFIncrementalStorePersistentStoreRequest"; -NSString * const AFIncrementalStoreFetchedObjectIDsKey = @"AFIncrementalStoreFetchedObjectsKey"; -NSString * const AFIncrementalStoreInsertedObjectIDsKey = @"AFIncrementalStoreInsertedObjectIDsKey"; -NSString * const AFIncrementalStoreUpdatedObjectIDsKey = @"AFIncrementalStoreUpdatedObjectIDsKey"; +NSString * const AFIncrementalStoreFetchedObjectsKey = @"AFIncrementalStoreFetchedObjectsKey"; +NSString * const AFIncrementalStoreInsertedObjectsKey = @"AFIncrementalStoreInsertedObjectIDsKey"; +NSString * const AFIncrementalStoreUpdatedObjectsKey = @"AFIncrementalStoreUpdatedObjectIDsKey"; NSString * const AFIncrementalStoreDeletedObjectIDsKey = @"AFIncrementalStoreDeletedObjectIDsKey"; static NSString * const kAFIncrementalStoreResourceIdentifierAttributeName = @"__af_resourceIdentifier"; @@ -114,15 +114,15 @@ + (NSManagedObjectModel *)model { - (void)notifyManagedObjectContext:(NSManagedObjectContext *)context aboutRequestOperation:(AFHTTPRequestOperation *)operation forFetchRequest:(NSFetchRequest *)fetchRequest - withFetchedObjectIDs:(NSArray *)fetchedObjectIDs + withFetchedObjects:(NSArray *)fetchedObjects { NSString *notificationName = [operation isFinished] ? AFIncrementalStoreContextDidFetchRemoteValues : AFIncrementalStoreContextWillFetchRemoteValues; NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; [userInfo setObject:operation forKey:AFIncrementalStoreRequestOperationKey]; [userInfo setObject:fetchRequest forKey:AFIncrementalStorePersistentStoreRequestKey]; - if ([operation isFinished] && fetchedObjectIDs) - [userInfo setObject:fetchedObjectIDs forKey:AFIncrementalStoreFetchedObjectIDsKey]; + if ([operation isFinished] && fetchedObjects) + [userInfo setObject:fetchedObjects forKey:AFIncrementalStoreFetchedObjectsKey]; [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:context userInfo:userInfo]; } @@ -130,8 +130,8 @@ - (void)notifyManagedObjectContext:(NSManagedObjectContext *)context - (void)notifyManagedObjectContext:(NSManagedObjectContext *)context aboutRequestOperations:(NSArray *)operations forSaveChangesRequest:(NSSaveChangesRequest *)saveChangesRequest - withInsertedObjectIDs:(NSArray *)insertedObjectIDs - withUpdatedObjectIDs:(NSArray *)updatedObjectIDs + withInsertedObjects:(NSArray *)insertedObjects + withUpdatedObjects:(NSArray *)updatedObjects withDeletedObjectIDs:(NSArray *)deletedObjectIDs { NSString *notificationName = [[operations lastObject] isFinished] ? AFIncrementalStoreContextDidSaveRemoteValues : AFIncrementalStoreContextWillSaveRemoteValues; @@ -139,10 +139,10 @@ - (void)notifyManagedObjectContext:(NSManagedObjectContext *)context NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; [userInfo setObject:operations forKey:AFIncrementalStoreRequestOperationKey]; [userInfo setObject:saveChangesRequest forKey:AFIncrementalStorePersistentStoreRequestKey]; - if (insertedObjectIDs) - [userInfo setObject:insertedObjectIDs forKey:AFIncrementalStoreInsertedObjectIDsKey]; - if (updatedObjectIDs) - [userInfo setObject:updatedObjectIDs forKey:AFIncrementalStoreUpdatedObjectIDsKey]; + if (insertedObjects) + [userInfo setObject:insertedObjects forKey:AFIncrementalStoreInsertedObjectsKey]; + if (updatedObjects) + [userInfo setObject:updatedObjects forKey:AFIncrementalStoreUpdatedObjectsKey]; if (deletedObjectIDs) [userInfo setObject:deletedObjectIDs forKey:AFIncrementalStoreDeletedObjectIDsKey]; @@ -341,21 +341,26 @@ - (id)executeFetchRequest:(NSFetchRequest *)fetchRequest NSLog(@"Error: %@", *error); } - NSMutableArray *tmpObjectIDs = [NSMutableArray array]; - for (NSManagedObject *object in managedObjects) - [tmpObjectIDs addObject:object.objectID]; + NSMutableArray *fetchedObjects = [NSMutableArray array]; + [context performBlockAndWait:^{ + for (NSManagedObject *obj in managedObjects) + { + NSManagedObject *contextObj = [context existingObjectWithID:obj.objectID error:NULL]; + [fetchedObjects addObject:contextObj]; + } + }]; - [self notifyManagedObjectContext:context aboutRequestOperation:operation forFetchRequest:fetchRequest withFetchedObjectIDs:tmpObjectIDs]; + [self notifyManagedObjectContext:context aboutRequestOperation:operation forFetchRequest:fetchRequest withFetchedObjects:fetchedObjects]; }]; }]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"Error: %@", error); - [self notifyManagedObjectContext:context aboutRequestOperation:operation forFetchRequest:fetchRequest withFetchedObjectIDs:nil]; + [self notifyManagedObjectContext:context aboutRequestOperation:operation forFetchRequest:fetchRequest withFetchedObjects:nil]; }]; operation.successCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); - [self notifyManagedObjectContext:context aboutRequestOperation:operation forFetchRequest:fetchRequest withFetchedObjectIDs:nil]; + [self notifyManagedObjectContext:context aboutRequestOperation:operation forFetchRequest:fetchRequest withFetchedObjects:nil]; [self.HTTPClient enqueueHTTPRequestOperation:operation]; } @@ -406,9 +411,9 @@ - (id)executeSaveChangesRequest:(NSSaveChangesRequest *)saveChangesRequest NSMutableArray *mutableOperations = [NSMutableArray array]; NSManagedObjectContext *backingContext = [self backingManagedObjectContext]; - __block NSMutableArray *insertedObjectIDs; + __block NSMutableArray *insertedObjects; if ([self.HTTPClient respondsToSelector:@selector(requestForInsertedObject:)]) { - insertedObjectIDs = [[NSMutableArray alloc] initWithCapacity:[[saveChangesRequest insertedObjects] count]]; + insertedObjects = [[NSMutableArray alloc] initWithCapacity:[[saveChangesRequest insertedObjects] count]]; for (NSManagedObject *insertedObject in [saveChangesRequest insertedObjects]) { NSURLRequest *request = [self.HTTPClient requestForInsertedObject:insertedObject]; AFHTTPRequestOperation *operation = [self.HTTPClient HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { @@ -425,7 +430,12 @@ - (id)executeSaveChangesRequest:(NSSaveChangesRequest *)saveChangesRequest }]; [context obtainPermanentIDsForObjects:[NSArray arrayWithObject:insertedObject] error:nil]; - [insertedObjectIDs addObject:[insertedObject objectID]]; + + [context performBlockAndWait:^{ + NSManagedObject *contextInsertedObj = [context existingObjectWithID:insertedObject.objectID error:NULL]; + [insertedObjects addObject:contextInsertedObj]; + }]; + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"Insert Error: %@", error); }]; @@ -434,9 +444,9 @@ - (id)executeSaveChangesRequest:(NSSaveChangesRequest *)saveChangesRequest } } - __block NSMutableArray *updatedObjectIDs; + __block NSMutableArray *updatedObjects; if ([self.HTTPClient respondsToSelector:@selector(requestForUpdatedObject:)]) { - updatedObjectIDs = [[NSMutableArray alloc] initWithCapacity:[[saveChangesRequest updatedObjects] count]]; + updatedObjects = [[NSMutableArray alloc] initWithCapacity:[[saveChangesRequest updatedObjects] count]]; for (NSManagedObject *updatedObject in [saveChangesRequest updatedObjects]) { NSURLRequest *request = [self.HTTPClient requestForUpdatedObject:updatedObject]; AFHTTPRequestOperation *operation = [self.HTTPClient HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { @@ -447,6 +457,11 @@ - (id)executeSaveChangesRequest:(NSSaveChangesRequest *)saveChangesRequest [backingObject setValuesForKeysWithDictionary:[updatedObject dictionaryWithValuesForKeys:nil]]; [backingContext save:nil]; }]; + + [context performBlockAndWait:^{ + NSManagedObject *contextUpdatedObj = [context existingObjectWithID:updatedObject.objectID error:NULL]; + [insertedObjects addObject:contextUpdatedObj]; + }]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"Update Error: %@", error); }]; @@ -477,16 +492,16 @@ - (id)executeSaveChangesRequest:(NSSaveChangesRequest *)saveChangesRequest } [self notifyManagedObjectContext:context aboutRequestOperations:mutableOperations forSaveChangesRequest:saveChangesRequest - withInsertedObjectIDs:nil - withUpdatedObjectIDs:nil + withInsertedObjects:nil + withUpdatedObjects:nil withDeletedObjectIDs:nil]; [self.HTTPClient enqueueBatchOfHTTPRequestOperations:mutableOperations progressBlock:nil completionBlock:^(NSArray *operations) { [self notifyManagedObjectContext:context aboutRequestOperations:operations forSaveChangesRequest:saveChangesRequest - withInsertedObjectIDs:insertedObjectIDs - withUpdatedObjectIDs:updatedObjectIDs + withInsertedObjects:insertedObjects + withUpdatedObjects:updatedObjects withDeletedObjectIDs:deletedObjectIDs]; }]; From 9494bcd1d7a9b5423d6557f196879583b5d75a56 Mon Sep 17 00:00:00 2001 From: Adam Price Date: Wed, 31 Oct 2012 18:11:05 -0400 Subject: [PATCH 3/3] Fixed a few typos --- AFIncrementalStore/AFFetchSaveManager.h | 4 ++-- AFIncrementalStore/AFIncrementalStore.m | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AFIncrementalStore/AFFetchSaveManager.h b/AFIncrementalStore/AFFetchSaveManager.h index 1d66226..0d00072 100644 --- a/AFIncrementalStore/AFFetchSaveManager.h +++ b/AFIncrementalStore/AFFetchSaveManager.h @@ -8,8 +8,8 @@ @class AFHTTPRequestOperation; -typedef void (^AFIncrementalStoreFetchCompletionBlock)(NSFetchRequest *fetchRequest, AFHTTPRequestOperation *operation, NSArray *fetchedObjectIDs); -typedef void (^AFIncrementalStoreSaveCompletionBlock)(NSSaveChangesRequest *saveChangesRequest, AFHTTPRequestOperation *operation, NSArray *insertedObjectIDs, NSArray *updatedObjectIDs, NSArray *deletedObjectIDs); +typedef void (^AFIncrementalStoreFetchCompletionBlock)(NSFetchRequest *fetchRequest, AFHTTPRequestOperation *operation, NSArray *fetchedObjects); +typedef void (^AFIncrementalStoreSaveCompletionBlock)(NSSaveChangesRequest *saveChangesRequest, AFHTTPRequestOperation *operation, NSArray *insertedObjects, NSArray *updatedObjects, NSArray *deletedObjectIDs); @interface AFFetchSaveManager : NSObject diff --git a/AFIncrementalStore/AFIncrementalStore.m b/AFIncrementalStore/AFIncrementalStore.m index 014b7af..ecf7d5d 100644 --- a/AFIncrementalStore/AFIncrementalStore.m +++ b/AFIncrementalStore/AFIncrementalStore.m @@ -460,7 +460,7 @@ - (id)executeSaveChangesRequest:(NSSaveChangesRequest *)saveChangesRequest [context performBlockAndWait:^{ NSManagedObject *contextUpdatedObj = [context existingObjectWithID:updatedObject.objectID error:NULL]; - [insertedObjects addObject:contextUpdatedObj]; + [updatedObjects addObject:contextUpdatedObj]; }]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"Update Error: %@", error);