mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-23 20:31:13 +08:00
Merge branch 'feature/635-accelerate-entity-cache' into development
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
180
Code/CoreData/RKEntityByAttributeCache.h
Normal file
180
Code/CoreData/RKEntityByAttributeCache.h
Normal 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
|
||||
279
Code/CoreData/RKEntityByAttributeCache.m
Normal file
279
Code/CoreData/RKEntityByAttributeCache.m
Normal 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
|
||||
133
Code/CoreData/RKEntityCache.h
Normal file
133
Code/CoreData/RKEntityCache.h
Normal 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
|
||||
141
Code/CoreData/RKEntityCache.m
Normal file
141
Code/CoreData/RKEntityCache.m
Normal 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
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
#import "RKManagedObjectCaching.h"
|
||||
#import "RKInMemoryEntityCache.h"
|
||||
|
||||
/**
|
||||
Provides a fast managed object cache where-in object instances are retained in
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user