Migrated to a single entity cache for the primary MOC instead of one per thread

This commit is contained in:
Blake Watters
2012-07-20 15:41:47 -04:00
parent 6b315fbd54
commit 093a76e7b2
12 changed files with 152 additions and 109 deletions

View File

@@ -139,21 +139,23 @@
- (BOOL)containsObjectWithAttributeValue:(id)attributeValue;
/**
Returns the first object with a matching value for the cache key attribute.
Returns the first object with a matching value for the cache key attribute
in a given managed object context.
@param attributeValue A value for the cache key attribute.
@return An object with the value of attribute matching attributeValue or nil.
*/
- (NSManagedObject *)objectWithAttributeValue:(id)attributeValue;
- (NSManagedObject *)objectWithAttributeValue:(id)attributeValue inContext:(NSManagedObjectContext *)context;
/**
Returns the collection of objects with a matching value for the cache key attribute.
Returns the collection of objects with a matching value for the cache key attribute
in a given managed object context.
@param attributeValue A value for the cache key attribute.
@return An array of objects with the value of attribute matching attributeValue or
an empty array.
*/
- (NSArray *)objectsWithAttributeValue:(id)attributeValue;
- (NSArray *)objectsWithAttributeValue:(id)attributeValue inContext:(NSManagedObjectContext *)context;
///-----------------------------------------------------------------------------
/// @name Managing Cached Objects

View File

@@ -45,11 +45,7 @@
selector:@selector(managedObjectContextDidChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:context];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:context];
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning:)
@@ -85,7 +81,7 @@
- (NSUInteger)countWithAttributeValue:(id)attributeValue
{
return [[self objectsWithAttributeValue:attributeValue] count];
return [[self objectsWithAttributeValue:attributeValue inContext:self.managedObjectContext] count];
}
- (BOOL)shouldCoerceAttributeToString:(NSString *)attributeValue
@@ -150,13 +146,7 @@
return (self.attributeValuesToObjectIDs != nil);
}
- (NSManagedObject *)objectWithAttributeValue:(id)attributeValue
{
NSArray *objects = [self objectsWithAttributeValue:attributeValue];
return ([objects count] > 0) ? [objects objectAtIndex:0] : nil;
}
- (NSManagedObject *)objectForObjectID:(NSManagedObjectID *)objectID
- (NSManagedObject *)objectForObjectID:(NSManagedObjectID *)objectID inContext:(NSManagedObjectContext *)context
{
/*
NOTE:
@@ -164,34 +154,58 @@
that will raise an exception when fired. existingObjectWithID:error: will return nil if the ID has been
deleted. objectRegisteredForID: is also an acceptable approach.
*/
NSError *error = nil;
NSManagedObject *object = [self.managedObjectContext existingObjectWithID:objectID error:&error];
__block NSError *error = nil;
__block NSManagedObject *object;
[context performBlockAndWait:^{
object = [context existingObjectWithID:objectID error:&error];
}];
if (! object) {
if (error) {
RKLogError(@"Failed to retrieve managed object with ID %@. Error %@\n%@", objectID, [error localizedDescription], [error userInfo]);
}
return nil;
}
return object;
}
- (NSArray *)objectsWithAttributeValue:(id)attributeValue
- (NSManagedObject *)objectWithAttributeValue:(id)attributeValue inContext:(NSManagedObjectContext *)context
{
NSArray *objects = [self objectsWithAttributeValue:attributeValue inContext:context];
return ([objects count] > 0) ? [objects objectAtIndex:0] : nil;
}
- (NSArray *)objectsWithAttributeValue:(id)attributeValue inContext:(NSManagedObjectContext *)context
{
attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
NSMutableArray *objectIDs = [self.attributeValuesToObjectIDs objectForKey:attributeValue];
if (objectIDs) {
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:[objectIDs count]];
for (NSManagedObjectID *objectID in objectIDs) {
NSManagedObject *object = [self objectForObjectID:objectID];
if (object) {
[objects addObject:object];
} else {
RKLogDebug(@"Evicting objectID association for attribute '%@'=>'%@' of Entity '%@': %@", self.attribute, attributeValue, self.entity.name, objectID);
[self removeObjectID:objectID forAttributeValue:attributeValue];
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = self.entity;
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"self in %@", objectIDs];
__block NSArray *objects;
__block NSError *error;
[context performBlockAndWait:^{
objects = [context executeFetchRequest:fetchRequest error:&error];
}];
if (! objects) {
RKLogWarning(@"Failed to retrieve cached objects. Execution failed for fetch request: %@", fetchRequest);
RKLogCoreDataError(error);
}
// NSMutableArray *objects = [NSMutableArray arrayWithCapacity:[objectIDs count]];
// for (NSManagedObjectID *objectID in objectIDs) {
// NSManagedObject *object = [self objectForObjectID:objectID inContext:context];
// if (object) {
// [objects addObject:object];
// } else {
// RKLogDebug(@"Evicting objectID association for attribute '%@'=>'%@' of Entity '%@': %@", self.attribute, attributeValue, self.entity.name, objectID);
// [self removeObjectID:objectID forAttributeValue:attributeValue];
// }
// }
return objects;
}
@@ -271,7 +285,7 @@
{
// Coerce to a string if possible
attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
return [[self objectsWithAttributeValue:attributeValue] count] > 0;
return [[self objectsWithAttributeValue:attributeValue inContext:self.managedObjectContext] count] > 0;
}
- (BOOL)containsObject:(NSManagedObject *)object
@@ -306,15 +320,6 @@
}
}
- (void)managedObjectContextDidSave:(NSNotification *)notification
{
// After the MOC has been saved, we flush to ensure any temporary
// objectID references are converted into permanent ID's on the next load.
// [self flush];
// TODO: We should not do this... better strategy?
}
- (void)didReceiveMemoryWarning:(NSNotification *)notification
{
[self flush];

View File

@@ -65,10 +65,11 @@
@param entity The entity to search the cache for instances of.
@param attributeName The attribute to search the cache for matches with.
@param attributeValue The value of the attribute to return a match for.
@param context The managed object from which to retrieve the cached results.
@return A matching managed object instance or nil.
@raise NSInvalidArgumentException Raised if instances of the entity and attribute have not been cached.
*/
- (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue;
- (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue inContext:(NSManagedObjectContext *)context;
/**
Retrieves all cached instances of a given entity where the specified attribute matches the given value.
@@ -76,10 +77,11 @@
@param entity The entity to search the cache for instances of.
@param attributeName The attribute to search the cache for matches with.
@param attributeValue The value of the attribute to return a match for.
@param context The managed object from which to retrieve the cached results.
@return All matching managed object instances or nil.
@raise NSInvalidArgumentException Raised if instances of the entity and attribute have not been cached.
*/
- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue;
- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue inContext:(NSManagedObjectContext *)context;
///-----------------------------------------------------------------------------
// @name Accessing Underlying Caches

View File

@@ -65,25 +65,25 @@
return (attributeCache && attributeCache.isLoaded);
}
- (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue
- (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue inContext:(NSManagedObjectContext *)context
{
NSAssert(entity, @"Cannot retrieve cached objects with a nil entity");
NSAssert(attributeName, @"Cannot retrieve cached objects by a nil entity");
RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attribute:attributeName];
if (attributeCache) {
return [attributeCache objectWithAttributeValue:attributeValue];
return [attributeCache objectWithAttributeValue:attributeValue inContext:context];
}
return nil;
}
- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue
- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue inContext:(NSManagedObjectContext *)context
{
NSAssert(entity, @"Cannot retrieve cached objects with a nil entity");
NSAssert(attributeName, @"Cannot retrieve cached objects by a nil entity");
RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attribute:attributeName];
if (attributeCache) {
return [attributeCache objectsWithAttributeValue:attributeValue];
return [attributeCache objectsWithAttributeValue:attributeValue inContext:context];
}
return [NSSet set];

View File

@@ -15,4 +15,14 @@
*/
@interface RKInMemoryManagedObjectCache : NSObject <RKManagedObjectCaching>
/**
Initializes the receiver with a managed object context that is to be observed
and used to populate the in memory cache. The receiver may then be used to fulfill
cache requests for child contexts of the given managed object context.
@param managedObjectContext The managed object context with which to initialize the receiver.
@return The receiver, initialized with the given managed object context.
*/
- (id)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
@end

View File

@@ -15,29 +15,36 @@
#undef RKLogComponent
#define RKLogComponent lcl_cRestKitCoreData
static NSString * const RKInMemoryObjectManagedObjectCacheThreadDictionaryKey = @"RKInMemoryObjectManagedObjectCacheThreadDictionaryKey";
//static NSString * const RKInMemoryObjectManagedObjectCacheThreadDictionaryKey = @"RKInMemoryObjectManagedObjectCacheThreadDictionaryKey";
@interface RKInMemoryManagedObjectCache ()
@property (nonatomic, retain) RKEntityCache *entityCache;
@end
@implementation RKInMemoryManagedObjectCache
- (RKEntityCache *)cacheForEntity:(NSEntityDescription *)entity inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
- (id)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
NSAssert(entity, @"Cannot find existing managed object without a target class");
NSAssert(managedObjectContext, @"Cannot find existing managed object with a context");
NSMutableDictionary *contextDictionary = [[[NSThread currentThread] threadDictionary] objectForKey:RKInMemoryObjectManagedObjectCacheThreadDictionaryKey];
if (! contextDictionary) {
contextDictionary = [NSMutableDictionary dictionaryWithCapacity:1];
[[[NSThread currentThread] threadDictionary] setObject:contextDictionary forKey:RKInMemoryObjectManagedObjectCacheThreadDictionaryKey];
}
NSNumber *hashNumber = [NSNumber numberWithUnsignedInteger:[managedObjectContext hash]];
RKEntityCache *entityCache = [contextDictionary objectForKey:hashNumber];
if (! entityCache) {
RKLogInfo(@"Creating thread-local entity cache for managed object context: %@", managedObjectContext);
entityCache = [[RKEntityCache alloc] initWithManagedObjectContext:managedObjectContext];
[contextDictionary setObject:entityCache forKey:hashNumber];
[entityCache release];
self = [super init];
if (self) {
self.entityCache = [[[RKEntityCache alloc] initWithManagedObjectContext:managedObjectContext] autorelease];
}
return self;
}
return entityCache;
- (id)init
{
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"%@ Failed to call designated initializer. Invoke initWithManagedObjectContext: instead.",
NSStringFromClass([self class])]
userInfo:nil];
}
- (void)dealloc
{
[_entityCache release];
[super dealloc];
}
- (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
@@ -45,15 +52,15 @@ static NSString * const RKInMemoryObjectManagedObjectCacheThreadDictionaryKey =
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
RKEntityCache *entityCache = [self cacheForEntity:entity inManagedObjectContext:managedObjectContext];
if (! [entityCache isEntity:entity cachedByAttribute:primaryKeyAttribute]) {
NSAssert(self.entityCache, @"Entity cache cannot be nil.");
if (! [self.entityCache isEntity:entity cachedByAttribute:primaryKeyAttribute]) {
RKLogInfo(@"Caching instances of Entity '%@' by primary key attribute '%@'", entity.name, primaryKeyAttribute);
[entityCache cacheObjectsForEntity:entity byAttribute:primaryKeyAttribute];
RKEntityByAttributeCache *attributeCache = [entityCache attributeCacheForEntity:entity attribute:primaryKeyAttribute];
[self.entityCache cacheObjectsForEntity:entity byAttribute:primaryKeyAttribute];
RKEntityByAttributeCache *attributeCache = [self.entityCache attributeCacheForEntity:entity attribute:primaryKeyAttribute];
RKLogTrace(@"Cached %ld objects", (long)[attributeCache count]);
}
return [entityCache objectForEntity:entity withAttribute:primaryKeyAttribute value:primaryKeyValue];
return [self.entityCache objectForEntity:entity withAttribute:primaryKeyAttribute value:primaryKeyValue inContext:managedObjectContext];
}
- (NSArray *)findInstancesOfEntity:(NSEntityDescription *)entity
@@ -61,33 +68,31 @@ static NSString * const RKInMemoryObjectManagedObjectCacheThreadDictionaryKey =
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
RKEntityCache *entityCache = [self cacheForEntity:entity inManagedObjectContext:managedObjectContext];
if (! [entityCache isEntity:entity cachedByAttribute:primaryKeyAttribute]) {
NSAssert(self.entityCache, @"Entity cache cannot be nil.");
if (! [self.entityCache isEntity:entity cachedByAttribute:primaryKeyAttribute]) {
RKLogInfo(@"Caching instances of Entity '%@' by primary key attribute '%@'", entity.name, primaryKeyAttribute);
[entityCache cacheObjectsForEntity:entity byAttribute:primaryKeyAttribute];
RKEntityByAttributeCache *attributeCache = [entityCache attributeCacheForEntity:entity attribute:primaryKeyAttribute];
[self.entityCache cacheObjectsForEntity:entity byAttribute:primaryKeyAttribute];
RKEntityByAttributeCache *attributeCache = [self.entityCache attributeCacheForEntity:entity attribute:primaryKeyAttribute];
RKLogTrace(@"Cached %ld objects", (long) [attributeCache count]);
}
return [entityCache objectsForEntity:entity withAttribute:primaryKeyAttribute value:primaryKeyValue];
return [self.entityCache objectsForEntity:entity withAttribute:primaryKeyAttribute value:primaryKeyValue inContext:managedObjectContext];
}
- (void)didFetchObject:(NSManagedObject *)object
{
RKEntityCache *entityCache = [self cacheForEntity:object.entity inManagedObjectContext:object.managedObjectContext];
[entityCache addObject:object];
[self.entityCache addObject:object];
}
- (void)didCreateObject:(NSManagedObject *)object
{
RKEntityCache *entityCache = [self cacheForEntity:object.entity inManagedObjectContext:object.managedObjectContext];
[entityCache addObject:object];
[self.entityCache addObject:object];
}
- (void)didDeleteObject:(NSManagedObject *)object
{
RKEntityCache *entityCache = [self cacheForEntity:object.entity inManagedObjectContext:object.managedObjectContext];
[entityCache removeObject:object];
[self.entityCache removeObject:object];
}
@end

View File

@@ -141,7 +141,7 @@ static RKManagedObjectStore *defaultStore = nil;
[self createPersistentStoreCoordinator];
[self createManagedObjectContexts];
_cacheStrategy = [RKInMemoryManagedObjectCache new];
_cacheStrategy = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:self.primaryManagedObjectContext];
// Ensure there is a search word observer
[RKSearchWordObserver sharedObserver];