Merge branch 'feature/635-accelerate-entity-cache' into development

This commit is contained in:
Blake Watters
2012-05-15 19:42:19 -04:00
34 changed files with 4112 additions and 732 deletions

View File

@@ -16,6 +16,14 @@
*/
extern NSString * const RKEntityDescriptionPrimaryKeyAttributeUserInfoKey;
/**
The substitution variable used in predicateForPrimaryKeyAttribute.
**Value**: @"PRIMARY_KEY_VALUE"
@see predicateForPrimaryKeyAttribute
*/
extern NSString * const RKEntityDescriptionPrimaryKeyAttributeValuePredicateSubstitutionVariable;
/**
Provides extensions to NSEntityDescription for various common tasks.
*/
@@ -34,6 +42,28 @@ extern NSString * const RKEntityDescriptionPrimaryKeyAttributeUserInfoKey;
Programmatically configured values take precedence over the user info
dictionary.
*/
@property(nonatomic, retain) NSString *primaryKeyAttribute;
@property (nonatomic, retain) NSString *primaryKeyAttribute;
/**
Returns a cached predicate specifying that the primary key attribute is equal to the $PRIMARY_KEY_VALUE
substitution variable.
This predicate is cached to avoid parsing overhead during object mapping operations
and must be evaluated using [NSPredicate predicateWithSubstitutionVariables:]
@return A cached predicate specifying the value of the primary key attribute is equal to the $PRIMARY_KEY_VALUE
substitution variable.
*/
- (NSPredicate *)predicateForPrimaryKeyAttribute;
/**
Returns a predicate specifying that the value of the primary key attribute is equal to a given
value. This predicate is constructed by evaluating the cached predicate returned by the
predicateForPrimaryKeyAttribute with a dictionary of substitution variables specifying that
$PRIMARY_KEY_VALUE is equal to the given value.
@return A predicate speciying that the value of the primary key attribute is equal to a given value.
*/
- (NSPredicate *)predicateForPrimaryKeyAttributeWithValue:(id)value;
@end

View File

@@ -10,10 +10,23 @@
#import "NSEntityDescription+RKAdditions.h"
NSString * const RKEntityDescriptionPrimaryKeyAttributeUserInfoKey = @"primaryKeyAttribute";
static char primaryKeyAttributeKey;
NSString * const RKEntityDescriptionPrimaryKeyAttributeValuePredicateSubstitutionVariable = @"PRIMARY_KEY_VALUE";
static char primaryKeyAttributeKey, primaryKeyPredicateKey;
@implementation NSEntityDescription (RKAdditions)
- (void)setPredicateForPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == $PRIMARY_KEY_VALUE", primaryKeyAttribute];
objc_setAssociatedObject(self,
&primaryKeyPredicateKey,
predicate,
OBJC_ASSOCIATION_RETAIN);
}
#pragma mark - Public
- (NSString *)primaryKeyAttribute
{
// Check for an associative object reference
@@ -22,6 +35,11 @@ static char primaryKeyAttributeKey;
// Fall back to the userInfo dictionary
if (! primaryKeyAttribute) {
primaryKeyAttribute = [self.userInfo valueForKey:RKEntityDescriptionPrimaryKeyAttributeUserInfoKey];
// If we have loaded from the user info, ensure we have a predicate
if (! [self predicateForPrimaryKeyAttribute]) {
[self setPredicateForPrimaryKeyAttribute:primaryKeyAttribute];
}
}
return primaryKeyAttribute;
@@ -32,7 +50,21 @@ static char primaryKeyAttributeKey;
objc_setAssociatedObject(self,
&primaryKeyAttributeKey,
primaryKeyAttribute,
OBJC_ASSOCIATION_RETAIN);
OBJC_ASSOCIATION_RETAIN);
[self setPredicateForPrimaryKeyAttribute:primaryKeyAttribute];
}
- (NSPredicate *)predicateForPrimaryKeyAttribute
{
return (NSPredicate *) objc_getAssociatedObject(self, &primaryKeyPredicateKey);
}
- (NSPredicate *)predicateForPrimaryKeyAttributeWithValue:(id)value
{
NSDictionary *variables = [NSDictionary dictionaryWithObject:value
forKey:RKEntityDescriptionPrimaryKeyAttributeValuePredicateSubstitutionVariable];
return [[self predicateForPrimaryKeyAttribute] predicateWithSubstitutionVariables:variables];
}
@end

View File

@@ -0,0 +1,180 @@
//
// RKEntityByAttributeCache.h
// RestKit
//
// Created by Blake Watters on 5/1/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import <CoreData/CoreData.h>
/**
Instances of RKEntityByAttributeCache provide an in-memory caching mechanism
for managed objects instances of an entity in a managed object context with
the value of one of the object's attributes acting as the cache key. When loaded,
the cache will retrieve all instances of an entity from the store and build a
dictionary mapping values for the given cache key attribute to the managed object
ID for all objects matching the value. The cache can then be used to quickly retrieve
objects by attribute value for the cache key without executing another fetch request
against the managed object context. This can provide a large performance improvement
when a large number of objects are being retrieved using a particular attribute as
the key.
RKEntityByAttributeCache instances are used by the RKEntityCache to provide
caching for multiple entities at once.
@see RKEntityCache
*/
@interface RKEntityByAttributeCache : NSObject
///-----------------------------------------------------------------------------
/// @name Creating a Cache
///-----------------------------------------------------------------------------
/**
Initializes the receiver with a given entity, attribute, and managed object context.
@param entity The Core Data entity description for the managed objects being cached.
@param attributeName The name of an attribute within the cached entity that acts as the cache key.
@param managedObjectContext The managed object context the cache retrieves the cached
objects from
@return The receiver, initialized with the given entity, attribute, and managed object
context.
*/
- (id)initWithEntity:(NSEntityDescription *)entity attribute:(NSString *)attributeName managedObjectContext:(NSManagedObjectContext *)context;
///-----------------------------------------------------------------------------
/// @name Getting Cache Identity
///-----------------------------------------------------------------------------
/**
The Core Data entity description for the managed objects being cached.
*/
@property (nonatomic, readonly) NSEntityDescription *entity;
/**
An attribute that is part of the cached entity that acts as the cache key.
*/
@property (nonatomic, readonly) NSString *attribute;
/**
The managed object context the receiver fetches cached objects from.
*/
@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.
*/
@property (nonatomic, assign) BOOL monitorsContextForChanges;
///-----------------------------------------------------------------------------
/// @name Loading and Flushing the Cache
///-----------------------------------------------------------------------------
/**
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.
*/
- (void)load;
/**
Flushes the cache by releasing all cache attribute value to managed object ID
associations.
*/
- (void)flush;
///-----------------------------------------------------------------------------
/// @name Inspecting Cache State
///-----------------------------------------------------------------------------
/**
A Boolean value indicating if the cache has loaded associations between cache
attribute values and managed object ID's.
*/
- (BOOL)isLoaded;
/**
Returns a count of the total number of cached objects.
*/
- (NSUInteger)count;
/**
Returns the total number of cached objects with a given value for
the attribute acting as the cache key.
@param attributeValue The value for the cache key attribute to retrieve
a count of the objects with a matching value.
@return The number of objects in the cache with the given value for the cache
attribute of the receiver.
*/
- (NSUInteger)countWithAttributeValue:(id)attributeValue;
/**
Returns the number of unique attribute values contained within the receiver.
@return The number of unique attribute values within the receiver.
*/
- (NSUInteger)countOfAttributeValues;
/**
Returns a Boolean value that indicates whether a given object is present
in the cache.
@param object An object.
@return YES if object is present in the cache, otherwise NO.
*/
- (BOOL)containsObject:(NSManagedObject *)object;
/**
Returns a Boolean value that indicates whether one of more objects is present
in the cache with a given value of the cache key attribute.
@param attributeValue The value with which to check the cache for objects with
a matching value.
@return YES if one or more objects with the given value for the cache key
attribute is present in the cache, otherwise NO.
*/
- (BOOL)containsObjectWithAttributeValue:(id)attributeValue;
/**
Returns the first object with a matching value for the cache key attribute.
@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;
/**
Returns the collection of objects with a matching value for the cache key attribute.
@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;
///-----------------------------------------------------------------------------
/// @name Managing Cached Objects
///-----------------------------------------------------------------------------
/**
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.
*/
- (void)addObject:(NSManagedObject *)object;
/**
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.
*/
- (void)removeObject:(NSManagedObject *)object;
@end

View File

@@ -0,0 +1,279 @@
//
// RKEntityByAttributeCache.m
// RestKit
//
// Created by Blake Watters on 5/1/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif
#import "RKEntityByAttributeCache.h"
#import "RKLog.h"
#import "RKObjectPropertyInspector.h"
#import "RKObjectPropertyInspector+CoreData.h"
// Set Logging Component
#undef RKLogComponent
#define RKLogComponent lcl_cRestKitCoreDataCache
@interface RKEntityByAttributeCache ()
@property (nonatomic, retain) NSMutableDictionary *attributeValuesToObjectIDs;
@end
@implementation RKEntityByAttributeCache
@synthesize entity = _entity;
@synthesize attribute = _attribute;
@synthesize managedObjectContext = _managedObjectContext;
@synthesize attributeValuesToObjectIDs = _attributeValuesToObjectIDs;
@synthesize monitorsContextForChanges = _monitorsContextForChanges;
- (id)initWithEntity:(NSEntityDescription *)entity attribute:(NSString *)attributeName managedObjectContext:(NSManagedObjectContext *)context
{
self = [self init];
if (self) {
_entity = [entity retain];
_attribute = [attributeName retain];
_managedObjectContext = [context retain];
_monitorsContextForChanges = YES;
[[NSNotificationCenter defaultCenter] addObserver:self
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:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
#endif
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_entity release];
[_attribute release];
[_managedObjectContext release];
[_attributeValuesToObjectIDs release];
[super dealloc];
}
- (NSUInteger)count
{
return [[[self.attributeValuesToObjectIDs allValues] valueForKeyPath:@"@sum.@count"] integerValue];
}
- (NSUInteger)countOfAttributeValues
{
return [self.attributeValuesToObjectIDs count];
}
- (NSUInteger)countWithAttributeValue:(id)attributeValue
{
return [[self objectsWithAttributeValue:attributeValue] count];
}
- (BOOL)shouldCoerceAttributeToString:(NSString *)attributeValue
{
if ([attributeValue isKindOfClass:[NSString class]] || [attributeValue isEqual:[NSNull null]]) {
return NO;
}
Class attributeType = [[RKObjectPropertyInspector sharedInspector] typeForProperty:self.attribute ofEntity:self.entity];
return [attributeType instancesRespondToSelector:@selector(stringValue)];
}
- (void)load
{
RKLogInfo(@"Loading entity cache for Entity '%@' by attribute '%@'", self.entity.name, self.attribute);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:self.entity];
[fetchRequest setResultType:NSManagedObjectIDResultType];
NSError *error = nil;
NSArray *objectIDs = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error) {
RKLogError(@"Failed to load entity cache: %@", error);
return;
}
[fetchRequest release];
self.attributeValuesToObjectIDs = [NSMutableDictionary dictionaryWithCapacity:[objectIDs count]];
for (NSManagedObjectID *objectID in objectIDs) {
NSError *error = nil;
NSManagedObject *object = [self.managedObjectContext existingObjectWithID:objectID error:&error];
if (! object && error) {
RKLogError(@"Failed to retrieve managed object with ID %@: %@", objectID, error);
}
[self addObject:object];
}
}
- (void)flush
{
RKLogInfo(@"Flushing entity cache for Entity '%@' by attribute '%@'", self.entity.name, self.attribute);
self.attributeValuesToObjectIDs = nil;
}
- (void)reload
{
[self flush];
[self load];
}
- (BOOL)isLoaded
{
return (self.attributeValuesToObjectIDs != nil);
}
- (NSManagedObject *)objectWithAttributeValue:(id)attributeValue
{
NSArray *objects = [self objectsWithAttributeValue:attributeValue];
return ([objects count] > 0) ? [objects objectAtIndex:0] : nil;
}
- (NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID {
/*
NOTE:
We use existingObjectWithID: as opposed to objectWithID: as objectWithID: can return us a fault
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];
if (! object && error) {
RKLogError(@"Failed to retrieve managed object with ID %@. Error %@\n%@", objectID, [error localizedDescription], [error userInfo]);
return nil;
}
return object;
}
- (NSArray *)objectsWithAttributeValue:(id)attributeValue
{
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 objectWithID:objectID];
if (object) [objects addObject:object];
}
return objects;
}
return [NSArray array];
}
- (void)addObject:(NSManagedObject *)object
{
NSAssert([object.entity isEqual:self.entity], @"Cannot add object with entity '%@' to cache with entity of '%@'", [[object entity] name], [self.entity name]);
id attributeValue = [object valueForKey:self.attribute];
// Coerce to a string if possible
attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
if (attributeValue) {
NSManagedObjectID *objectID = [object objectID];
NSMutableArray *objectIDs = [self.attributeValuesToObjectIDs objectForKey:attributeValue];
if (objectIDs) {
if (! [objectIDs containsObject:objectID]) {
[objectIDs addObject:objectID];
}
} else {
objectIDs = [NSMutableArray arrayWithObject:objectID];
}
if (nil == self.attributeValuesToObjectIDs) self.attributeValuesToObjectIDs = [NSMutableDictionary dictionary];
[self.attributeValuesToObjectIDs setValue:objectIDs forKey:attributeValue];
} else {
RKLogWarning(@"Unable to add object with nil value for attribute '%@': %@", self.attribute, object);
}
}
- (void)removeObject:(NSManagedObject *)object
{
NSAssert([object.entity isEqual:self.entity], @"Cannot remove object with entity '%@' from cache with entity of '%@'", [[object entity] name], [self.entity name]);
id attributeValue = [object valueForKey:self.attribute];
// Coerce to a string if possible
attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
if (attributeValue) {
NSManagedObjectID *objectID = [object objectID];
NSMutableArray *objectIDs = [self.attributeValuesToObjectIDs objectForKey:attributeValue];
if (objectIDs && [objectIDs containsObject:objectID]) {
[objectIDs removeObject:objectID];
}
} else {
RKLogWarning(@"Unable to remove object with nil value for attribute '%@': %@", self.attribute, object);
}
}
- (BOOL)containsObjectWithAttributeValue:(id)attributeValue
{
// Coerce to a string if possible
attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
return [[self objectsWithAttributeValue:attributeValue] count] > 0;
}
- (BOOL)containsObject:(NSManagedObject *)object
{
if (! [object.entity isEqual:self.entity]) return NO;
id attributeValue = [object valueForKey:self.attribute];
// Coerce to a string if possible
attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
return [[self objectsWithAttributeValue:attributeValue] containsObject:object];
}
- (void)managedObjectContextDidChange:(NSNotification *)notification
{
if (self.monitorsContextForChanges == NO) return;
NSDictionary *userInfo = notification.userInfo;
NSSet *insertedObjects = [userInfo objectForKey:NSInsertedObjectsKey];
NSSet *updatedObjects = [userInfo objectForKey:NSUpdatedObjectsKey];
NSSet *deletedObjects = [userInfo objectForKey:NSDeletedObjectsKey];
RKLogTrace(@"insertedObjects=%@, updatedObjects=%@, deletedObjects=%@", insertedObjects, updatedObjects, deletedObjects);
NSMutableSet *objectsToAdd = [NSMutableSet setWithSet:insertedObjects];
[objectsToAdd unionSet:updatedObjects];
for (NSManagedObject *object in objectsToAdd) {
if ([object.entity isEqual:self.entity]) {
[self addObject:object];
}
}
for (NSManagedObject *object in deletedObjects) {
if ([object.entity isEqual:self.entity]) {
[self removeObject:object];
}
}
}
- (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];
}
- (void)didReceiveMemoryWarning:(NSNotification *)notification
{
[self flush];
}
@end

View File

@@ -0,0 +1,133 @@
//
// RKEntityCache.h
// RestKit
//
// Created by Blake Watters on 5/2/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import <CoreData/CoreData.h>
@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.
*/
@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.
*/
- (id)initWithManagedObjectContext:(NSManagedObjectContext *)context;
/**
The managed object context with which the receiver is associated.
*/
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
///-----------------------------------------------------------------------------
/// @name Caching Objects by Attribute
///-----------------------------------------------------------------------------
/**
Caches all instances of an entity using the value for an attribute as the cache key.
@param entity The entity to cache all instances of.
@param attributeName The attribute to cache the instances by.
*/
- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttribute:(NSString *)attributeName;
/**
Returns a Boolean value indicating if all instances of an entity have been cached by a given attribute name.
@param entity The entity to check the cache status of.
@param attributeName The attribute to check the cache status with.
@return YES if the cache has been loaded with instances with the given attribute, else NO.
*/
- (BOOL)isEntity:(NSEntityDescription *)entity cachedByAttribute:(NSString *)attributeName;
/**
Retrieves the first cached instance of a given entity where the specified attribute matches the given value.
@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.
@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;
/**
Retrieves all cached instances of a given entity where the specified attribute matches the given value.
@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.
@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;
///-----------------------------------------------------------------------------
// @name Accessing Underlying Caches
///-----------------------------------------------------------------------------
/**
Retrieves the underlying entity attribute cache for a given entity and attribute.
@param entity The entity to retrieve the entity attribute cache object for.
@param attributeName The attribute to retrieve the entity attribute cache object for.
@return The entity attribute cache for the given entity and attribute, or nil if none was found.
*/
- (RKEntityByAttributeCache *)attributeCacheForEntity:(NSEntityDescription *)entity attribute:(NSString *)attributeName;
/**
Retrieves all entity attributes caches for a given entity.
@param entity The entity to retrieve the collection of entity attribute caches for.
@return An array of entity attribute cache objects for the given entity or an empty array if none were found.
*/
- (NSArray *)attributeCachesForEntity:(NSEntityDescription *)entity;
///-----------------------------------------------------------------------------
// @name Managing the Cache
///-----------------------------------------------------------------------------
/**
Flushes the entity cache by sending a flush message to each entity attribute cache
contained within the receiver.
@see [RKEntityByAttributeCache flush]
*/
- (void)flush;
/**
Adds a given object 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.
*/
- (void)addObject:(NSManagedObject *)object;
/**
Removed a given object 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.
*/
- (void)removeObject:(NSManagedObject *)object;
@end

View File

@@ -0,0 +1,141 @@
//
// RKEntityCache.m
// RestKit
//
// Created by Blake Watters on 5/2/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import "RKEntityCache.h"
#import "RKEntityByAttributeCache.h"
@interface RKEntityCache ()
@property (nonatomic, retain) NSMutableSet *attributeCaches;
@end
@implementation RKEntityCache
@synthesize managedObjectContext = _managedObjectContext;
@synthesize attributeCaches = _attributeCaches;
- (id)initWithManagedObjectContext:(NSManagedObjectContext *)context
{
NSAssert(context, @"Cannot initialize entity cache with a nil context");
self = [super init];
if (self) {
_managedObjectContext = [context retain];
_attributeCaches = [[NSMutableSet alloc] init];
}
return self;
}
- (id)init
{
return [self initWithManagedObjectContext:nil];
}
- (void)dealloc
{
[_managedObjectContext release];
[_attributeCaches release];
[super dealloc];
}
- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttribute:(NSString *)attributeName
{
NSAssert(entity, @"Cannot cache objects for a nil entity");
NSAssert(attributeName, @"Cannot cache objects without an attribute");
RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attribute:attributeName];
if (attributeCache && !attributeCache.isLoaded) {
[attributeCache load];
} else {
attributeCache = [[RKEntityByAttributeCache alloc] initWithEntity:entity attribute:attributeName managedObjectContext:self.managedObjectContext];
[attributeCache load];
[self.attributeCaches addObject:attributeCache];
[attributeCache release];
}
}
- (BOOL)isEntity:(NSEntityDescription *)entity cachedByAttribute:(NSString *)attributeName
{
NSAssert(entity, @"Cannot check cache status for a nil entity");
NSAssert(attributeName, @"Cannot check cache status for a nil attribute");
RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attribute:attributeName];
return (attributeCache && attributeCache.isLoaded);
}
- (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue
{
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 nil;
}
- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue
{
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 [NSSet set];
}
- (RKEntityByAttributeCache *)attributeCacheForEntity:(NSEntityDescription *)entity attribute:(NSString *)attributeName
{
NSAssert(entity, @"Cannot retrieve attribute cache for a nil entity");
NSAssert(attributeName, @"Cannot retrieve attribute cache for a nil attribute");
for (RKEntityByAttributeCache *cache in self.attributeCaches) {
if ([cache.entity isEqual:entity] && [cache.attribute isEqualToString:attributeName]) {
return cache;
}
}
return nil;
}
- (NSSet *)attributeCachesForEntity:(NSEntityDescription *)entity
{
NSAssert(entity, @"Cannot retrieve attribute caches for a nil entity");
NSMutableSet *set = [NSMutableSet set];
for (RKEntityByAttributeCache *cache in self.attributeCaches) {
if ([cache.entity isEqual:entity]) {
[set addObject:cache];
}
}
return [NSSet setWithSet:set];
}
- (void)flush
{
[self.attributeCaches makeObjectsPerformSelector:@selector(flush)];
}
- (void)addObject:(NSManagedObject *)object
{
NSAssert(object, @"Cannot add a nil object to the cache");
NSArray *attributeCaches = [self attributeCachesForEntity:object.entity];
for (RKEntityByAttributeCache *cache in attributeCaches) {
[cache addObject:object];
}
}
- (void)removeObject:(NSManagedObject *)object
{
NSAssert(object, @"Cannot remove a nil object from the cache");
NSArray *attributeCaches = [self attributeCachesForEntity:object.entity];
for (RKEntityByAttributeCache *cache in attributeCaches) {
[cache removeObject:object];
}
}
@end

View File

@@ -39,10 +39,18 @@
}
}
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setFetchLimit:1];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"%K = %@", primaryKeyAttribute, searchValue]];
// Use cached predicate if primary key matches
NSPredicate *predicate = nil;
if ([entity.primaryKeyAttribute isEqualToString:primaryKeyAttribute]) {
predicate = [entity predicateForPrimaryKeyAttributeWithValue:searchValue];
} else {
// Parse a predicate
predicate = [NSPredicate predicateWithFormat:@"%K = %@", primaryKeyAttribute, searchValue];
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = entity;
fetchRequest.fetchLimit = 1;
fetchRequest.predicate = predicate;
NSArray *objects = [NSManagedObject executeFetchRequest:fetchRequest inContext:managedObjectContext];
RKLogDebug(@"Found objects '%@' using fetchRequest '%@'", objects, fetchRequest);
[fetchRequest release];

View File

@@ -1,83 +0,0 @@
//
// RKInMemoryEntityCache.h
// RestKit
//
// Created by Jeff Arena on 1/24/12.
// Copyright (c) 2009-2012 RestKit. All rights reserved.
//
#import <CoreData/CoreData.h>
/**
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.
*/
@interface RKInMemoryEntityCache : NSObject
/**
The managed object context from which objects will be cached.
*/
@property(nonatomic, readonly) NSManagedObjectContext *managedObjectContext;
/// @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.
*/
- (id)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
/// @name Caching Objects by Attribute
/**
Retrieves all objects within the cache
*/
- (NSMutableDictionary *)cachedObjectsForEntity:(NSEntityDescription *)entity
byAttribute:(NSString *)attributeName
inContext:(NSManagedObjectContext *)managedObjectContext;
/**
*/
- (NSManagedObject *)cachedObjectForEntity:(NSEntityDescription *)entity
withAttribute:(NSString *)attributeName
value:(id)attributeValue
inContext:(NSManagedObjectContext *)managedObjectContext;
/**
Caches all instances of an entity in a given managed object context by the value
*/
- (void)cacheObjectsForEntity:(NSEntityDescription *)entity
byAttribute:(NSString *)attributeName
inContext:(NSManagedObjectContext *)managedObjectContext;
/**
*/
- (void)cacheObject:(NSManagedObject *)managedObject
byAttribute:(NSString *)attributeName
inContext:(NSManagedObjectContext *)managedObjectContext;
/**
*/
- (void)cacheObject:(NSEntityDescription *)entity
byAttribute:(NSString *)attributeName
value:(id)primaryKeyValue
inContext:(NSManagedObjectContext *)managedObjectContext;
/**
*/
- (void)expireCacheEntryForObject:(NSManagedObject *)managedObject
byAttribute:(NSString *)attributeName
inContext:(NSManagedObjectContext *)managedObjectContext;
/**
*/
- (void)expireCacheEntriesForEntity:(NSEntityDescription *)entity;
@end

View File

@@ -1,291 +0,0 @@
//
// RKInMemoryEntityCache.m
// RestKit
//
// Created by Jeff Arena on 1/24/12.
// Copyright (c) 2009-2012 RestKit. All rights reserved.
//
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif
#import "RKInMemoryEntityCache.h"
#import "NSManagedObject+ActiveRecord.h"
#import "../ObjectMapping/RKObjectPropertyInspector.h"
#import "RKObjectPropertyInspector+CoreData.h"
#import "RKLog.h"
// Set Logging Component
#undef RKLogComponent
#define RKLogComponent lcl_cRestKitCoreData
@interface RKInMemoryEntityCache ()
@property(nonatomic, retain) NSMutableArray *attributeCaches;
@property(nonatomic, retain) NSMutableDictionary *entityCache;
- (BOOL)shouldCoerceAttributeToString:(NSString *)attribute forEntity:(NSEntityDescription *)entity;
- (NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID inContext:(NSManagedObjectContext *)managedObjectContext;
@end
@implementation RKInMemoryEntityCache
@synthesize entityCache = _entityCache;
@synthesize managedObjectContext = _managedObjectContext;
@synthesize attributeCaches = _attributeCaches;
- (id)init {
self = [super init];
if (self) {
_entityCache = [[NSMutableDictionary alloc] init];
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
#endif
}
return self;
}
- (id)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
self = [self init];
if (self) {
_managedObjectContext = [managedObjectContext retain];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_entityCache release];
[_managedObjectContext release];
[_attributeCaches release];
[super dealloc];
}
- (void)didReceiveMemoryWarning {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextObjectsDidChangeNotification
object:nil];
[_entityCache removeAllObjects];
}
- (NSMutableDictionary *)cachedObjectsForEntity:(NSEntityDescription *)entity
byAttribute:(NSString *)attributeName
inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(entity, @"Cannot retrieve cached objects without an entity");
NSAssert(attributeName, @"Cannot retrieve cached objects without an attributeName");
NSAssert(managedObjectContext, @"Cannot retrieve cached objects without a managedObjectContext");
NSMutableDictionary *cachedObjectsForEntity = [_entityCache objectForKey:entity.name];
if (cachedObjectsForEntity == nil) {
[self cacheObjectsForEntity:entity byAttribute:attributeName inContext:managedObjectContext];
cachedObjectsForEntity = [_entityCache objectForKey:entity.name];
}
return cachedObjectsForEntity;
}
- (NSManagedObject *)cachedObjectForEntity:(NSEntityDescription *)entity
withAttribute:(NSString *)attributeName
value:(id)attributeValue
inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(entity, @"Cannot retrieve a cached object without an entity");
NSAssert(attributeName, @"Cannot retrieve a cached object without a mapping");
NSAssert(attributeValue, @"Cannot retrieve a cached object without a primaryKeyValue");
NSAssert(managedObjectContext, @"Cannot retrieve a cached object without a managedObjectContext");
NSMutableDictionary *cachedObjectsForEntity = [self cachedObjectsForEntity:entity
byAttribute:attributeName
inContext:managedObjectContext];
// NOTE: We coerce the primary key into a string (if possible) for convenience. Generally
// primary keys are expressed either as a number of a string, so this lets us support either case interchangeably
id lookupValue = [attributeValue respondsToSelector:@selector(stringValue)] ? [attributeValue stringValue] : attributeValue;
NSManagedObjectID *objectID = [cachedObjectsForEntity objectForKey:lookupValue];
NSManagedObject *object = nil;
if (objectID) {
object = [self objectWithID:objectID inContext:managedObjectContext];
}
return object;
}
- (void)cacheObjectsForEntity:(NSEntityDescription *)entity
byAttribute:(NSString *)attributeName
inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(entity, @"Cannot cache objects without an entity");
NSAssert(attributeName, @"Cannot cache objects without an attributeName");
NSAssert(managedObjectContext, @"Cannot cache objects without a managedObjectContext");
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setResultType:NSManagedObjectIDResultType];
NSArray *objectIds = [NSManagedObject executeFetchRequest:fetchRequest inContext:managedObjectContext];
[fetchRequest release];
RKLogInfo(@"Caching all %ld %@ objectsIDs to thread local storage", (long) [objectIds count], entity.name);
NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
if ([objectIds count] > 0) {
BOOL coerceToString = [self shouldCoerceAttributeToString:attributeName forEntity:entity];
for (NSManagedObjectID* theObjectID in objectIds) {
NSManagedObject* theObject = [self objectWithID:theObjectID inContext:managedObjectContext];
id attributeValue = [theObject valueForKey:attributeName];
// Coerce to a string if possible
attributeValue = coerceToString ? [attributeValue stringValue] : attributeValue;
if (attributeValue) {
[dictionary setObject:theObjectID forKey:attributeValue];
}
}
}
[_entityCache setObject:dictionary forKey:entity.name];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(objectsDidChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:managedObjectContext];
}
- (void)cacheObject:(NSManagedObject *)managedObject byAttribute:(NSString *)attributeName inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(managedObject, @"Cannot cache an object without a managedObject");
NSAssert(attributeName, @"Cannot cache an object without a mapping");
NSAssert(managedObjectContext, @"Cannot cache an object without a managedObjectContext");
NSManagedObjectID *objectID = [managedObject objectID];
if (objectID) {
NSEntityDescription *entity = managedObject.entity;
BOOL coerceToString = [self shouldCoerceAttributeToString:attributeName forEntity:entity];
id attributeValue = [managedObject valueForKey:attributeName];
// Coerce to a string if possible
attributeValue = coerceToString ? [attributeValue stringValue] : attributeValue;
if (attributeValue) {
NSMutableDictionary *cachedObjectsForEntity = [self cachedObjectsForEntity:entity
byAttribute:attributeName
inContext:managedObjectContext];
[cachedObjectsForEntity setObject:objectID forKey:attributeValue];
}
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(objectsDidChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:managedObjectContext];
}
- (void)cacheObject:(NSEntityDescription *)entity byAttribute:(NSString *)attributeName value:(id)attributeValue inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(entity, @"Cannot cache an object without an entity");
NSAssert(attributeName, @"Cannot cache an object without a mapping");
NSAssert(managedObjectContext, @"Cannot cache an object without a managedObjectContext");
// NOTE: We coerce the primary key into a string (if possible) for convenience. Generally
// primary keys are expressed either as a number or a string, so this lets us support either case interchangeably
id lookupValue = [attributeValue respondsToSelector:@selector(stringValue)] ? [attributeValue stringValue] : attributeValue;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setFetchLimit:1];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"%K = %@", attributeName, lookupValue]];
[fetchRequest setResultType:NSManagedObjectIDResultType];
NSArray *objectIds = [NSManagedObject executeFetchRequest:fetchRequest inContext:managedObjectContext];
[fetchRequest release];
NSManagedObjectID *objectID = nil;
if ([objectIds count] > 0) {
objectID = [objectIds objectAtIndex:0];
if (objectID && lookupValue) {
NSMutableDictionary *cachedObjectsForEntity = [self cachedObjectsForEntity:entity
byAttribute:attributeName
inContext:managedObjectContext];
[cachedObjectsForEntity setObject:objectID forKey:lookupValue];
}
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(objectsDidChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:managedObjectContext];
}
- (void)expireCacheEntryForObject:(NSManagedObject *)managedObject byAttribute:(NSString *)attributeName inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(managedObject, @"Cannot expire cache entry for an object without a managedObject");
NSAssert(attributeName, @"Cannot expire cache entry for an object without a mapping");
NSAssert(managedObjectContext, @"Cannot expire cache entry for an object without a managedObjectContext");
NSEntityDescription *entity = managedObject.entity;
BOOL coerceToString = [self shouldCoerceAttributeToString:attributeName forEntity:entity];
id attributeValue = [managedObject valueForKey:attributeName];
// Coerce to a string if possible
attributeValue = coerceToString ? [attributeValue stringValue] : attributeValue;
if (attributeValue) {
NSMutableDictionary *cachedObjectsForEntity = [self cachedObjectsForEntity:entity
byAttribute:attributeName
inContext:managedObjectContext];
[cachedObjectsForEntity removeObjectForKey:attributeValue];
if ([cachedObjectsForEntity count] == 0) {
[self expireCacheEntriesForEntity:entity];
}
}
}
- (void)expireCacheEntriesForEntity:(NSEntityDescription *)entity {
NSAssert(entity, @"Cannot expire cache entry for an entity without an entity");
RKLogTrace(@"About to expire cache for entity name=%@", entity.name);
[_entityCache removeObjectForKey:entity.name];
}
#pragma mark Helper Methods
- (BOOL)shouldCoerceAttributeToString:(NSString *)attribute forEntity:(NSEntityDescription *)entity {
Class attributeType = [[RKObjectPropertyInspector sharedInspector] typeForProperty:attribute ofEntity:entity];
return [attributeType instancesRespondToSelector:@selector(stringValue)];
}
- (NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(objectID, @"Cannot fetch a managedObject with a nil objectID");
NSAssert(managedObjectContext, @"Cannot fetch a managedObject with a nil managedObjectContext");
/*
NOTE:
We use existingObjectWithID: as opposed to objectWithID: as objectWithID: can return us a fault
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 = [managedObjectContext existingObjectWithID:objectID error:&error];
if (! object && error) {
RKLogError(@"Failed to retrieve managed object with ID %@: %@", objectID, error);
}
return object;
}
#pragma mark Notifications
- (void)objectsDidChange:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
NSSet *insertedObjects = [userInfo objectForKey:NSInsertedObjectsKey];
NSSet *deletedObjects = [userInfo objectForKey:NSDeletedObjectsKey];
RKLogTrace(@"insertedObjects=%@, deletedObjects=%@", insertedObjects, deletedObjects);
NSMutableSet *entitiesToExpire = [NSMutableSet set];
for (NSManagedObject *object in insertedObjects) {
[entitiesToExpire addObject:object.entity];
}
for (NSManagedObject *object in deletedObjects) {
[entitiesToExpire addObject:object.entity];
}
for (NSEntityDescription *entity in entitiesToExpire) {
[self expireCacheEntriesForEntity:entity];
}
}
@end

View File

@@ -7,7 +7,6 @@
//
#import "RKManagedObjectCaching.h"
#import "RKInMemoryEntityCache.h"
/**
Provides a fast managed object cache where-in object instances are retained in

View File

@@ -7,6 +7,8 @@
//
#import "RKInMemoryManagedObjectCache.h"
#import "NSEntityDescription+RKAdditions.h"
#import "RKEntityCache.h"
#import "RKLog.h"
// Set Logging Component
@@ -17,27 +19,59 @@ static NSString * const RKInMemoryObjectManagedObjectCacheThreadDictionaryKey =
@implementation RKInMemoryManagedObjectCache
- (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext {
- (RKEntityCache *)cacheForEntity:(NSEntityDescription *)entity inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
NSAssert(entity, @"Cannot find existing managed object without a target class");
NSAssert(primaryKeyAttribute, @"Cannot find existing managed object instance without mapping");
NSAssert(primaryKeyValue, @"Cannot find existing managed object by primary key without a value");
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]];
RKInMemoryEntityCache *cache = [contextDictionary objectForKey:hashNumber];
if (! cache) {
cache = [[RKInMemoryEntityCache alloc] initWithManagedObjectContext:managedObjectContext];
[contextDictionary setObject:cache forKey:hashNumber];
[cache release];
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];
}
return [cache cachedObjectForEntity:entity withAttribute:primaryKeyAttribute value:primaryKeyValue inContext:managedObjectContext];
return entityCache;
}
- (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
RKEntityCache *entityCache = [self cacheForEntity:entity inManagedObjectContext:managedObjectContext];
if (! [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];
RKLogTrace(@"Cached %ld objects", (long) [attributeCache count]);
}
return [entityCache objectForEntity:entity withAttribute:primaryKeyAttribute value:primaryKeyValue];
}
- (void)didFetchObject:(NSManagedObject *)object
{
RKEntityCache *entityCache = [self cacheForEntity:object.entity inManagedObjectContext:object.managedObjectContext];
[entityCache addObject:object];
}
- (void)didCreateObject:(NSManagedObject *)object
{
RKEntityCache *entityCache = [self cacheForEntity:object.entity inManagedObjectContext:object.managedObjectContext];
[entityCache addObject:object];
}
- (void)didDeleteObject:(NSManagedObject *)object
{
RKEntityCache *entityCache = [self cacheForEntity:object.entity inManagedObjectContext:object.managedObjectContext];
[entityCache removeObject:object];
}
@end

View File

@@ -16,6 +16,8 @@
*/
@protocol RKManagedObjectCaching
@required
/**
Retrieves a model object from the object store given a Core Data entity and
the primary key attribute and value for the desired object.
@@ -32,4 +34,27 @@
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
@optional
/**
Tells the receiver that an object was fetched and should be added to the cache.
@param object The object that was fetched from a managed object context.
*/
- (void)didFetchObject:(NSManagedObject *)object;
/**
Tells the receiver that an object was created and should be added to the cache.
@param object The object that was created in a managed object context.
*/
- (void)didCreateObject:(NSManagedObject *)object;
/**
Tells the receiver that an object was deleted and should be removed to the cache.
@param object The object that was deleted from a managed object context.
*/
- (void)didDeleteObject:(NSManagedObject *)object;
@end

View File

@@ -160,19 +160,34 @@
NSString* keyPathForPrimaryKeyElement = primaryKeyAttributeMapping.sourceKeyPath;
if (keyPathForPrimaryKeyElement) {
primaryKeyValue = [mappableData valueForKeyPath:keyPathForPrimaryKeyElement];
} else {
RKLogWarning(@"Unable to find source attribute for primaryKeyAttribute '%@': unable to find existing object instances by primary key.", primaryKeyAttribute);
}
}
}
// If we have found the primary key attribute & value, try to find an existing instance to update
if (primaryKeyAttribute && primaryKeyValue) {
object = [self.objectStore.cacheStrategy findInstanceOfEntity:entity
withPrimaryKeyAttribute:self.primaryKeyAttribute value:primaryKeyValue inManagedObjectContext:[self.objectStore managedObjectContextForCurrentThread]];
if (primaryKeyAttribute && primaryKeyValue && NO == [primaryKeyValue isEqual:[NSNull null]]) {
object = [self.objectStore.cacheStrategy findInstanceOfEntity:entity
withPrimaryKeyAttribute:primaryKeyAttribute
value:primaryKeyValue
inManagedObjectContext:[self.objectStore managedObjectContextForCurrentThread]];
if (object && [self.objectStore.cacheStrategy respondsToSelector:@selector(didFetchObject:)]) {
[self.objectStore.cacheStrategy didFetchObject:object];
}
}
if (object == nil) {
object = [[[NSManagedObject alloc] initWithEntity:entity
insertIntoManagedObjectContext:[_objectStore managedObjectContextForCurrentThread]] autorelease];
if (primaryKeyAttribute && primaryKeyValue && ![primaryKeyValue isEqual:[NSNull null]]) {
[object setValue:primaryKeyValue forKey:primaryKeyAttribute];
}
if ([self.objectStore.cacheStrategy respondsToSelector:@selector(didCreateObject:)]) {
[self.objectStore.cacheStrategy didCreateObject:object];
}
}
return object;
}

View File

@@ -119,7 +119,7 @@
manually invoke processPendingChanges to prevent recreating objects with the same primary key.
See https://github.com/RestKit/RestKit/issues/661
*/
[[[(RKManagedObjectMapping *)self.objectMapping objectStore] managedObjectContextForCurrentThread] processPendingChanges];
// [[[(RKManagedObjectMapping *)self.objectMapping objectStore] managedObjectContextForCurrentThread] processPendingChanges];
[self connectRelationships];
}
return success;

View File

@@ -126,8 +126,7 @@ static RKManagedObjectStore *defaultObjectStore = nil;
}
NSMutableArray* allManagedObjectModels = [NSMutableArray arrayWithObject:nilOrManagedObjectModel];
_managedObjectModel = [[NSManagedObjectModel modelByMergingModels:allManagedObjectModels] retain];
_delegate = delegate;
_delegate = delegate;
if (nilOrNameOfSeedDatabaseInMainBundle) {
[self createStoreIfNecessaryUsingSeedDatabase:nilOrNameOfSeedDatabaseInMainBundle];
@@ -136,7 +135,7 @@ static RKManagedObjectStore *defaultObjectStore = nil;
[self createPersistentStoreCoordinator];
self.primaryManagedObjectContext = [[self newManagedObjectContext] autorelease];
_cacheStrategy = [RKFetchRequestManagedObjectCache new];
_cacheStrategy = [RKInMemoryManagedObjectCache new];
// Ensure there is a search word observer
[RKSearchWordObserver sharedObserver];