Permalink
Browse files

Rework caches and connections to improve performance. refs #1232

* Eliminate redundant use observers across `RKEntityByAttributeCache` instances. Instead, `RKInMemoryManagedObjectCache` does all observing.
* Adjust caches to work primarilly off of `NSSet` of managed objects instead of single instances
* Rework caches to perform asynchronous cache management activities instead of blocking. Add `completion` blocks for callbacks on async activities.
* Switch `RKInMemoryManagedObjectCache` to use its own private context instead of the persistent store context to avoid potential deadlocks during async cache management
* Rework `RKRelationshipConnectionOperation` to handle an array of `RKConnectionDescription` objects instead of a single one. This reduces the amount of operations minted in proportion to the number of connections used.
  • Loading branch information...
1 parent f005f03 commit 5b2e613632c04be9372ab2ca697788ce5cee4928 @blakewatters blakewatters committed Apr 30, 2013
@@ -68,10 +68,11 @@
@property (nonatomic, readonly) NSManagedObjectContext *managedObjectContext;
/**
- A Boolean value determining if the receiever monitors the managed object context
- for changes and updates the cache entries using the notifications emitted.
+ The queue on which to dispatch callbacks for asynchronous operations. When `nil`, the main queue is used.
+
+ **Default**: `nil`
*/
-@property (nonatomic, assign) BOOL monitorsContextForChanges;
+@property (nonatomic, assign) dispatch_queue_t callbackQueue;
///-------------------------------------
/// @name Loading and Flushing the Cache
@@ -81,14 +82,17 @@
Loads the cache by finding all instances of the configured entity and building
an association between the value of the cached attribute's value and the
managed object ID for the object.
+
+ @param completion A block to execute when the cache has finished loading.
*/
-- (void)load;
+- (void)load:(void (^)(void))completion;
/**
- Flushes the cache by releasing all cache attribute value to managed object ID
- associations.
+ Flushes the cache by releasing all cache attribute value to managed object ID associations.
+
+ @param completion A block to execute when the cache has finished flushing.
*/
-- (void)flush;
+- (void)flush:(void (^)(void))completion;
///-----------------------------
/// @name Inspecting Cache State
@@ -160,21 +164,36 @@
///------------------------------
/**
- Adds a managed object to the cache.
+ Asynchronously adds a managed object to the cache.
The object must be an instance of the cached entity.
@param object The managed object to add to the cache.
+ @param completion An optional block to execute once the object has been added to the cache.
*/
-- (void)addObject:(NSManagedObject *)object;
+- (void)addObjects:(NSSet *)managedObjects completion:(void (^)(void))completion;
/**
- Removes a managed object from the cache.
+ Asynchronously removes a managed object from the cache.
The object must be an instance of the cached entity.
@param object The managed object to remove from the cache.
+ @param completion An optional block to execute once the object has been removed from the cache.
*/
-- (void)removeObject:(NSManagedObject *)object;
+- (void)removeObjects:(NSSet *)managedObjects completion:(void (^)(void))completion;
@end
+
+/*
+ Deprecated in 0.20.1
+
+ All methods below now accept a completion block
+ */
+@interface RKEntityByAttributeCache (Deprecations)
+- (void)load DEPRECATED_ATTRIBUTE; // use `load:`
+- (void)flush DEPRECATED_ATTRIBUTE; // use `flush:`
+- (void)addObject:(NSManagedObject *)object DEPRECATED_ATTRIBUTE; // use `addObjects:completion:`
+- (void)removeObject:(NSManagedObject *)object DEPRECATED_ATTRIBUTE; // use `removeObjects:completion:`
+@property (nonatomic, assign) BOOL monitorsContextForChanges DEPRECATED_ATTRIBUTE; // No longer applies. Consumers are responsible for context change monitoring. Handled by `RKInMemoryManagedObjectCache`
+@end

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -23,25 +23,19 @@
@class RKEntityByAttributeCache;
/**
- Instances of RKInMemoryEntityCache provide an in-memory caching mechanism for
- objects in a Core Data managed object context. Managed objects can be cached by
- attribute for fast retrieval without repeatedly hitting the Core Data persistent store.
- This can provide a substantial speed advantage over issuing fetch requests
- in cases where repeated look-ups of the same data are performed using a small set
- of attributes as the query key. Internally, the cache entries are maintained as
- references to the NSManagedObjectID of corresponding cached objects.
+ Instances of `RKEntityCache` provide an in-memory caching mechanism for objects in a Core Data managed object context. Managed objects can be cached by attribute for fast retrieval without repeatedly hitting the Core Data persistent store. This can provide a substantial speed advantage over issuing fetch requests in cases where repeated look-ups of the same data are performed using a small set of attributes as the query key. Internally, the cache entries are maintained as references to the `NSManagedObjectID` of corresponding cached objects.
*/
@interface RKEntityCache : NSObject
-///-----------------------------------------------------------------------------
+///-----------------------------
/// @name Initializing the Cache
-///-----------------------------------------------------------------------------
+///-----------------------------
/**
Initializes the receiver with a managed object context containing the entity instances to be cached.
@param context The managed object context containing objects to be cached.
- @returns self, initialized with context.
+ @returns The receiver, initialized with the given context.
*/
- (id)initWithManagedObjectContext:(NSManagedObjectContext *)context;
@@ -50,6 +44,17 @@
*/
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
+///-------------------------------------
+/// @name Configuring the Callback Queue
+///-------------------------------------
+
+/**
+ The queue on which to dispatch callbacks for asynchronous operations. When `nil`, the main queue is used.
+
+ **Default**: `nil`
+ */
+@property (nonatomic, assign) dispatch_queue_t callbackQueue;
+
///------------------------------------
/// @name Caching Objects by Attributes
///------------------------------------
@@ -60,7 +65,7 @@
@param entity The entity to cache all instances of.
@param attributeNames The attributes to cache the instances by.
*/
-- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttributes:(NSArray *)attributeNames;
+- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttributes:(NSArray *)attributeNames completion:(void (^)(void))completion;
/**
Returns a Boolean value indicating if all instances of an entity have been cached by a given attribute name.
@@ -119,27 +124,41 @@
///-----------------------------------------------------------------------------
/**
- Flushes the entity cache by sending a flush message to each entity attribute cache
- contained within the receiver.
+ Flushes the entity cache by sending a flush message to each entity attribute cache contained within the receiver.
+ @param completion An optional block to be executed when the flush has completed.
@see [RKEntityByAttributeCache flush]
*/
-- (void)flush;
+- (void)flush:(void (^)(void))completion;
/**
- Adds a given object to all entity attribute caches for the object's entity contained
- within the receiver.
+ Adds the given set of objects to all entity attribute caches for the object's entity contained within the receiver.
- @param object The object to add to the appropriate entity attribute caches.
+ @param objects The set of objects to add to the appropriate entity attribute caches.
+ @param completion An optional block to be executed when the object addition has completed.
*/
-- (void)addObject:(NSManagedObject *)object;
+- (void)addObjects:(NSSet *)objects completion:(void (^)(void))completion;
/**
- Removed a given object from all entity attribute caches for the object's entity contained
- within the receiver.
+ Removes the given set of objects from all entity attribute caches for the object's entity contained within the receiver.
- @param object The object to remove from the appropriate entity attribute caches.
+ @param objects The set of objects to remove from the appropriate entity attribute caches.
+ @param completion An optional block to be executed when the object removal has completed.
*/
-- (void)removeObject:(NSManagedObject *)object;
+- (void)removeObjects:(NSSet *)objects completion:(void (^)(void))completion;
+
+/**
+ Returns a Boolean value that indicates if the receiver contains the given object in any of its attribute caches.
+
+ @param managedObject The object to check for.
+ @return `YES` if the receiver contains the given object in one or more of its caches, else `NO`.
+ */
+- (BOOL)containsObject:(NSManagedObject *)managedObject;
+
+@end
+// Deprecated in v0.20.1
+@interface RKEntityCache (Deprecations)
+- (void)addObject:(NSManagedObject *)object DEPRECATED_ATTRIBUTE; // use `addObjects:completion:`
+- (void)removeObject:(NSManagedObject *)object DEPRECATED_ATTRIBUTE; // use `removeObjects:completion:`
@end
@@ -44,16 +44,17 @@ - (id)init
return [self initWithManagedObjectContext:nil];
}
-- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttributes:(NSArray *)attributeNames
+- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttributes:(NSArray *)attributeNames completion:(void (^)(void))completion
{
NSParameterAssert(entity);
NSParameterAssert(attributeNames);
RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attributes:attributeNames];
if (attributeCache && !attributeCache.isLoaded) {
- [attributeCache load];
+ [attributeCache load:completion];
} else {
attributeCache = [[RKEntityByAttributeCache alloc] initWithEntity:entity attributes:attributeNames managedObjectContext:self.managedObjectContext];
- [attributeCache load];
+ attributeCache.callbackQueue = self.callbackQueue;
+ [attributeCache load:completion];
[self.attributeCaches addObject:attributeCache];
}
}
@@ -118,27 +119,112 @@ - (NSSet *)attributeCachesForEntity:(NSEntityDescription *)entity
return [NSSet setWithSet:set];
}
-- (void)flush
+- (void)waitForDispatchGroup:(dispatch_group_t)dispatchGroup withCompletionBlock:(void (^)(void))completion
{
- [self.attributeCaches makeObjectsPerformSelector:@selector(flush)];
+ if (completion) {
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
+ dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
+#if !OS_OBJECT_USE_OBJC
+ dispatch_release(dispatchGroup);
+#endif
+ dispatch_async(self.callbackQueue ?: dispatch_get_main_queue(), completion);
+ });
+ }
+}
+
+- (void)flush:(void (^)(void))completion
+{
+ dispatch_group_t dispatchGroup = completion ? dispatch_group_create() : NULL;
+ for (RKEntityByAttributeCache *cache in self.attributeCaches) {
+ if (dispatchGroup) dispatch_group_enter(dispatchGroup);
+ [cache flush:^{
+ if (dispatchGroup) dispatch_group_leave(dispatchGroup);
+ }];
+ }
+ if (dispatchGroup) [self waitForDispatchGroup:dispatchGroup withCompletionBlock:completion];
}
-- (void)addObject:(NSManagedObject *)object
+- (void)addObject:(NSManagedObject *)object completion:(void (^)(void))completion
{
NSAssert(object, @"Cannot add a nil object to the cache");
+ dispatch_group_t dispatchGroup = completion ? dispatch_group_create() : NULL;
NSArray *attributeCaches = [self attributeCachesForEntity:object.entity];
+ NSSet *objects = [NSSet setWithObject:object];
for (RKEntityByAttributeCache *cache in attributeCaches) {
- [cache addObject:object];
- }
+ if (dispatchGroup) dispatch_group_enter(dispatchGroup);
+ [cache addObjects:objects completion:^{
+ if (dispatchGroup) dispatch_group_leave(dispatchGroup);
+ }];
+ }
+ if (dispatchGroup) [self waitForDispatchGroup:dispatchGroup withCompletionBlock:completion];
}
-- (void)removeObject:(NSManagedObject *)object
+- (void)removeObject:(NSManagedObject *)object completion:(void (^)(void))completion
{
NSAssert(object, @"Cannot remove a nil object from the cache");
NSArray *attributeCaches = [self attributeCachesForEntity:object.entity];
+ NSSet *objects = [NSSet setWithObject:object];
+ dispatch_group_t dispatchGroup = completion ? dispatch_group_create() : NULL;
for (RKEntityByAttributeCache *cache in attributeCaches) {
- [cache removeObject:object];
+ if (dispatchGroup) dispatch_group_enter(dispatchGroup);
+ [cache removeObjects:objects completion:^{
+ if (dispatchGroup) dispatch_group_leave(dispatchGroup);
+ }];
+ }
+ if (dispatchGroup) [self waitForDispatchGroup:dispatchGroup withCompletionBlock:completion];
+}
+
+- (void)addObjects:(NSSet *)objects completion:(void (^)(void))completion
+{
+ dispatch_group_t dispatchGroup = completion ? dispatch_group_create() : NULL;
+ NSSet *distinctEntities = [objects valueForKeyPath:@"entity"];
+ for (NSEntityDescription *entity in distinctEntities) {
+ NSArray *attributeCaches = [self attributeCachesForEntity:entity];
+ if ([attributeCaches count]) {
+ NSMutableSet *objectsToAdd = [NSMutableSet set];
+ for (NSManagedObject *managedObject in objects) {
+ if ([managedObject.entity isEqual:entity]) [objectsToAdd addObject:managedObject];
+ }
+ for (RKEntityByAttributeCache *cache in attributeCaches) {
+ if (dispatchGroup) dispatch_group_enter(dispatchGroup);
+ [cache addObjects:objectsToAdd completion:^{
+ if (dispatchGroup) dispatch_group_leave(dispatchGroup);
+ }];
+ }
+ }
+ }
+ if (dispatchGroup) [self waitForDispatchGroup:dispatchGroup withCompletionBlock:completion];
+}
+
+- (void)removeObjects:(NSSet *)objects completion:(void (^)(void))completion
+{
+ dispatch_group_t dispatchGroup = completion ? dispatch_group_create() : NULL;
+ NSSet *distinctEntities = [objects valueForKeyPath:@"entity"];
+ for (NSEntityDescription *entity in distinctEntities) {
+ NSArray *attributeCaches = [self attributeCachesForEntity:entity];
+ if ([attributeCaches count]) {
+ NSMutableSet *objectsToRemove = [NSMutableSet set];
+ for (NSManagedObject *managedObject in objects) {
+ if ([managedObject.entity isEqual:entity]) [objectsToRemove addObject:managedObject];
+ }
+ for (RKEntityByAttributeCache *cache in attributeCaches) {
+ if (dispatchGroup) dispatch_group_enter(dispatchGroup);
+ [cache removeObjects:objectsToRemove completion:^{
+ if (dispatchGroup) dispatch_group_leave(dispatchGroup);
+ }];
+ }
+ }
+ }
+ if (dispatchGroup) [self waitForDispatchGroup:dispatchGroup withCompletionBlock:completion];
+}
+
+- (BOOL)containsObject:(NSManagedObject *)managedObject
+{
+ for (RKEntityByAttributeCache *attributeCache in [self attributeCachesForEntity:managedObject.entity]) {
+ if ([attributeCache containsObject:managedObject]) return YES;
}
+
+ return NO;
}
@end
Oops, something went wrong.

0 comments on commit 5b2e613

Please sign in to comment.