First functional cut at new Core Data cacheing implementation. Tests clean, needs polishing.

This commit is contained in:
Blake Watters
2012-05-04 22:15:30 -04:00
parent e4c33ab395
commit fbcef6abd5
19 changed files with 3304 additions and 12 deletions

View File

@@ -0,0 +1,61 @@
//
// RKEntityByAttributeCache.h
// RestKit
//
// Created by Blake Watters on 5/1/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import <CoreData/CoreData.h>
// RKManagedObjectContext
// Maybe RKManagedObjectContextCache | RKEntityCache | RKEntityByAttributeCache
// TODO: Better name... RKEntityAttributeCache ??
@interface RKEntityByAttributeCache : NSObject
///-----------------------------------------------------------------------------
/// @name Creating a Cache
///-----------------------------------------------------------------------------
- (id)initWithEntity:(NSEntityDescription *)entity attribute:(NSString *)attributeName managedObjectContext:(NSManagedObjectContext *)context;
///-----------------------------------------------------------------------------
/// @name Getting Cache Identity
///-----------------------------------------------------------------------------
@property (nonatomic, readonly) NSEntityDescription *entity;
@property (nonatomic, readonly) NSString *attribute;
@property (nonatomic, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, assign) BOOL monitorsContextForChanges;
///-----------------------------------------------------------------------------
/// @name Loading and Flushing the Cache
///-----------------------------------------------------------------------------
- (void)load;
- (void)flush;
///-----------------------------------------------------------------------------
/// @name Inspecting Cache State
///-----------------------------------------------------------------------------
- (BOOL)isLoaded;
- (NSUInteger)count;
- (NSUInteger)countWithAttributeValue:(id)attributeValue;
- (BOOL)containsObject:(NSManagedObject *)object;
- (BOOL)containsObjectWithAttributeValue:(id)attributeValue;
// Retrieve the object with the value for the attribute
- (NSManagedObject *)objectWithAttributeValue:(id)attributeValue;
- (NSSet *)objectsWithAttributeValue:(id)attributeValue;
///-----------------------------------------------------------------------------
/// @name Managing Cached Objects
///-----------------------------------------------------------------------------
- (void)addObject:(NSManagedObject *)object;
- (void)removeObject:(NSManagedObject *)object;
@end

View File

@@ -0,0 +1,270 @@
//
// 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_cRestKitCoreData
@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 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
{
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
{
self.attributeValuesToObjectIDs = nil;
}
- (void)reload
{
[self flush];
[self load];
}
- (BOOL)isLoaded
{
return (self.attributeValuesToObjectIDs != nil);
}
- (NSManagedObject *)objectWithAttributeValue:(id)attributeValue
{
return [[self objectsWithAttributeValue:attributeValue] anyObject];
}
- (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;
}
- (NSSet *)objectsWithAttributeValue:(id)attributeValue
{
attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
NSMutableSet *set = [self.attributeValuesToObjectIDs valueForKey:attributeValue];
if (set) {
NSSet *objectIDs = [NSSet setWithSet:set];
NSMutableSet *objects = [NSMutableSet setWithCapacity:[objectIDs count]];
for (NSManagedObjectID *objectID in objectIDs) {
NSManagedObject *object = [self objectWithID:objectID];
if (object) [objects addObject:object];
}
return objects;
}
return [NSSet set];
}
- (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];
BOOL isTemporary = [objectID isTemporaryID];
NSMutableSet *set = [self.attributeValuesToObjectIDs valueForKey:attributeValue];
if (set) {
[set addObject:objectID];
} else {
set = [NSMutableSet setWithObject:objectID];
}
if (nil == self.attributeValuesToObjectIDs) self.attributeValuesToObjectIDs = [NSMutableDictionary dictionary];
[self.attributeValuesToObjectIDs setValue:set 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];
NSMutableSet *set = [self.attributeValuesToObjectIDs valueForKey:attributeValue];
if (set) {
[set 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,91 @@
//
// 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;
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
/// @name Cacheing 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 the 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.
*/
- (NSSet *)objectsForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue;
// @name Accessing Underlying Caches
- (RKEntityByAttributeCache *)attributeCacheForEntity:(NSEntityDescription *)entity attribute:(NSString *)attributeName;
- (NSSet *)attributeCachesForEntity:(NSEntityDescription *)entity;
// @name Managing the Cache
/**
Empties the cache by releasing all cached objects.
*/
- (void)flush;
- (void)addObject:(NSManagedObject *)object;
- (void)removeObject:(NSManagedObject *)object;
@end

View File

@@ -0,0 +1,122 @@
//
// 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
{
self = [self init];
if (self) {
_managedObjectContext = [context retain];
_attributeCaches = [[NSMutableSet alloc] init];
}
return self;
}
- (void)dealloc
{
[_managedObjectContext release];
[_attributeCaches release];
[super dealloc];
}
- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttribute:(NSString *)attributeName
{
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
{
RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attribute:attributeName];
return (attributeCache && attributeCache.isLoaded);
}
- (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue
{
RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attribute:attributeName];
if (attributeCache) {
return [attributeCache objectWithAttributeValue:attributeValue];
}
return nil;
}
- (NSSet *)objectsForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue
{
RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attribute:attributeName];
if (attributeCache) {
return [attributeCache objectsWithAttributeValue:attributeValue];
}
return [NSSet set];
}
- (RKEntityByAttributeCache *)attributeCacheForEntity:(NSEntityDescription *)entity attribute:(NSString *)attributeName
{
for (RKEntityByAttributeCache *cache in self.attributeCaches) {
if ([cache.entity isEqual:entity] && [cache.attribute isEqualToString:attributeName]) {
return cache;
}
}
return nil;
}
- (NSSet *)attributeCachesForEntity:(NSEntityDescription *)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
{
NSSet *attributeCaches = [self attributeCachesForEntity:object.entity];
for (RKEntityByAttributeCache *cache in attributeCaches) {
[cache addObject:object];
}
}
- (void)removeObject:(NSManagedObject *)object
{
NSSet *attributeCaches = [self attributeCachesForEntity:object.entity];
for (RKEntityByAttributeCache *cache in attributeCaches) {
[cache removeObject:object];
}
}
@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,7 @@
//
#import "RKInMemoryManagedObjectCache.h"
#import "RKEntityCache.h"
#import "RKLog.h"
// Set Logging Component
@@ -30,14 +31,23 @@ static NSString * const RKInMemoryObjectManagedObjectCacheThreadDictionaryKey =
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];
if (! [entityCache isEntity:entity cachedByAttribute:primaryKeyAttribute]) {
RKLogInfo(@"Cacheing instances of Entity '%@' by attribute '%@'", entity.name, primaryKeyAttribute);
[entityCache cacheObjectsForEntity:entity byAttribute:primaryKeyAttribute];
RKEntityByAttributeCache *attributeCache = [entityCache attributeCacheForEntity:entity attribute:primaryKeyAttribute];
RKLogTrace(@"Cached %d objects", [attributeCache count]);
}
return [entityCache objectForEntity:entity withAttribute:primaryKeyAttribute value:primaryKeyValue];
}
@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,10 @@
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
@optional
- (void)didFetchObject:(NSManagedObject *)object;
- (void)didCreateObject:(NSManagedObject *)object;
- (void)didDeleteObject:(NSManagedObject *)object;
@end

View File

@@ -167,7 +167,7 @@
}
// If we have found the primary key attribute & value, try to find an existing instance to update
if (primaryKeyAttribute && primaryKeyValue) {
if (primaryKeyAttribute && primaryKeyValue && NO == [primaryKeyValue isEqual:[NSNull null]]) {
object = [self.objectStore.cacheStrategy findInstanceOfEntity:entity
withPrimaryKeyAttribute:self.primaryKeyAttribute value:primaryKeyValue inManagedObjectContext:[self.objectStore managedObjectContextForCurrentThread]];
}

View File

@@ -7,3 +7,4 @@ gem "thin", "~> 1.3.1"
gem 'xcoder', :git => "git://github.com/rayh/xcoder.git"
gem 'restkit', :git => 'git://github.com/RestKit/RestKit-Gem.git'
gem 'ruby-debug19'
gem 'faker', '1.0.1'

View File

@@ -32,6 +32,9 @@ GEM
columnize (0.3.6)
daemons (1.1.4)
eventmachine (0.12.10)
faker (1.0.1)
i18n (~> 0.4)
i18n (0.6.0)
json (1.6.6)
linecache19 (0.5.12)
ruby_core_source (>= 0.1.4)
@@ -65,6 +68,7 @@ PLATFORMS
DEPENDENCIES
bundler (~> 1.1.0)
faker (= 1.0.1)
rake (~> 0.9.0)
restkit!
ruby-debug19

View File

@@ -181,3 +181,25 @@ desc "Validate a branch is ready for merging by checking for common issues"
task :validate => [:build, 'docs:check', 'uispec:all'] do
puts "Project state validated successfully. Proceed with merge."
end
namespace :payload do
task :generate do
require 'json'
require 'faker'
ids = (1..25).to_a
child_ids = (50..100).to_a
child_counts = (10..25).to_a
hash = ids.inject({'parents' => []}) do |hash, parent_id|
child_count = child_counts.sample
children = (0..child_count).collect do
{'name' => Faker::Name.name, 'childID' => child_ids.sample}
end
parent = {'parentID' => parent_id, 'name' => Faker::Name.name, 'children' => children}
hash['parents'] << parent
hash
end
File.open('payload.json', 'w+') { |f| f << hash.to_json }
puts "Generated payload at: payload.json"
end
end

View File

@@ -523,10 +523,24 @@
259C3027151280A1003066A2 /* grayArrow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 25EC1AE314F8022600C3CF3F /* grayArrow@2x.png */; };
259C3028151280A1003066A2 /* whiteArrow.png in Resources */ = {isa = PBXBuildFile; fileRef = 25EC1AE414F8022600C3CF3F /* whiteArrow.png */; };
259C3029151280A1003066A2 /* whiteArrow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 25EC1AE514F8022600C3CF3F /* whiteArrow@2x.png */; };
259D983C154F6C90008C90F5 /* benchmark_parents_and_children.json in Resources */ = {isa = PBXBuildFile; fileRef = 259D983B154F6C90008C90F5 /* benchmark_parents_and_children.json */; };
259D983D154F6C90008C90F5 /* benchmark_parents_and_children.json in Resources */ = {isa = PBXBuildFile; fileRef = 259D983B154F6C90008C90F5 /* benchmark_parents_and_children.json */; };
259D9847154F8744008C90F5 /* RKBenchmark.h in Headers */ = {isa = PBXBuildFile; fileRef = 259D9845154F8744008C90F5 /* RKBenchmark.h */; settings = {ATTRIBUTES = (Public, ); }; };
259D9848154F8744008C90F5 /* RKBenchmark.h in Headers */ = {isa = PBXBuildFile; fileRef = 259D9845154F8744008C90F5 /* RKBenchmark.h */; settings = {ATTRIBUTES = (Public, ); }; };
259D9849154F8744008C90F5 /* RKBenchmark.m in Sources */ = {isa = PBXBuildFile; fileRef = 259D9846154F8744008C90F5 /* RKBenchmark.m */; };
259D984A154F8744008C90F5 /* RKBenchmark.m in Sources */ = {isa = PBXBuildFile; fileRef = 259D9846154F8744008C90F5 /* RKBenchmark.m */; };
259D98541550C69A008C90F5 /* RKEntityByAttributeCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 259D98521550C69A008C90F5 /* RKEntityByAttributeCache.h */; };
259D98551550C69A008C90F5 /* RKEntityByAttributeCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 259D98521550C69A008C90F5 /* RKEntityByAttributeCache.h */; };
259D98561550C69A008C90F5 /* RKEntityByAttributeCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 259D98531550C69A008C90F5 /* RKEntityByAttributeCache.m */; };
259D98571550C69A008C90F5 /* RKEntityByAttributeCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 259D98531550C69A008C90F5 /* RKEntityByAttributeCache.m */; };
259D985A1550C6BE008C90F5 /* RKInMemoryEntityAttributeCacheTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 259D98591550C6BE008C90F5 /* RKInMemoryEntityAttributeCacheTest.m */; };
259D985B1550C6BE008C90F5 /* RKInMemoryEntityAttributeCacheTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 259D98591550C6BE008C90F5 /* RKInMemoryEntityAttributeCacheTest.m */; };
259D985E155218E5008C90F5 /* RKEntityCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 259D985C155218E4008C90F5 /* RKEntityCache.h */; };
259D985F155218E5008C90F5 /* RKEntityCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 259D985C155218E4008C90F5 /* RKEntityCache.h */; };
259D9860155218E5008C90F5 /* RKEntityCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 259D985D155218E4008C90F5 /* RKEntityCache.m */; };
259D9861155218E5008C90F5 /* RKEntityCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 259D985D155218E4008C90F5 /* RKEntityCache.m */; };
259D986415521B20008C90F5 /* RKEntityCacheTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 259D986315521B1F008C90F5 /* RKEntityCacheTest.m */; };
259D986515521B20008C90F5 /* RKEntityCacheTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 259D986315521B1F008C90F5 /* RKEntityCacheTest.m */; };
25A2476E153E667E003240B6 /* RKCacheTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 25A2476D153E667E003240B6 /* RKCacheTest.m */; };
25A2476F153E667E003240B6 /* RKCacheTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 25A2476D153E667E003240B6 /* RKCacheTest.m */; };
25A34245147D8AAA0009758D /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25A34244147D8AAA0009758D /* Security.framework */; };
@@ -1049,8 +1063,16 @@
257ABAB41511371C00CCAA76 /* NSManagedObject+RKAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSManagedObject+RKAdditions.h"; sourceTree = "<group>"; };
257ABAB51511371D00CCAA76 /* NSManagedObject+RKAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSManagedObject+RKAdditions.m"; sourceTree = "<group>"; };
259C301615128079003066A2 /* RestKitResources.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RestKitResources.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
259D983B154F6C90008C90F5 /* benchmark_parents_and_children.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = benchmark_parents_and_children.json; sourceTree = "<group>"; };
259D9845154F8744008C90F5 /* RKBenchmark.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKBenchmark.h; path = Testing/RKBenchmark.h; sourceTree = "<group>"; };
259D9846154F8744008C90F5 /* RKBenchmark.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RKBenchmark.m; path = Testing/RKBenchmark.m; sourceTree = "<group>"; };
259D98521550C69A008C90F5 /* RKEntityByAttributeCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKEntityByAttributeCache.h; sourceTree = "<group>"; };
259D98531550C69A008C90F5 /* RKEntityByAttributeCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKEntityByAttributeCache.m; sourceTree = "<group>"; };
259D98581550C6BE008C90F5 /* RKInMemoryEntityAttributeCacheTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKInMemoryEntityAttributeCacheTest.h; sourceTree = "<group>"; };
259D98591550C6BE008C90F5 /* RKInMemoryEntityAttributeCacheTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKInMemoryEntityAttributeCacheTest.m; sourceTree = "<group>"; };
259D985C155218E4008C90F5 /* RKEntityCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKEntityCache.h; sourceTree = "<group>"; };
259D985D155218E4008C90F5 /* RKEntityCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKEntityCache.m; sourceTree = "<group>"; };
259D986315521B1F008C90F5 /* RKEntityCacheTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKEntityCacheTest.m; sourceTree = "<group>"; };
25A2476D153E667E003240B6 /* RKCacheTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKCacheTest.m; sourceTree = "<group>"; };
25A34244147D8AAA0009758D /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; };
25B408241491CDDB00F21111 /* RKDirectory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKDirectory.h; sourceTree = "<group>"; };
@@ -1381,6 +1403,10 @@
257ABAB51511371D00CCAA76 /* NSManagedObject+RKAdditions.m */,
25079C6D151B93DB00266AE7 /* NSEntityDescription+RKAdditions.h */,
25079C6E151B93DB00266AE7 /* NSEntityDescription+RKAdditions.m */,
259D98521550C69A008C90F5 /* RKEntityByAttributeCache.h */,
259D98531550C69A008C90F5 /* RKEntityByAttributeCache.m */,
259D985C155218E4008C90F5 /* RKEntityCache.h */,
259D985D155218E4008C90F5 /* RKEntityCache.m */,
);
path = CoreData;
sourceTree = "<group>";
@@ -1685,6 +1711,9 @@
25E36E0115195CED00F9E448 /* RKFetchRequestMappingCacheTest.m */,
25079C75151B952200266AE7 /* NSEntityDescription+RKAdditionsTest.m */,
25DB7507151BD551009F01AF /* NSManagedObject+ActiveRecordTest.m */,
259D98581550C6BE008C90F5 /* RKInMemoryEntityAttributeCacheTest.h */,
259D98591550C6BE008C90F5 /* RKInMemoryEntityAttributeCacheTest.m */,
259D986315521B1F008C90F5 /* RKEntityCacheTest.m */,
);
name = CoreData;
path = Logic/CoreData;
@@ -1712,6 +1741,7 @@
25160FD01456F2330060A5C5 /* JSON */ = {
isa = PBXGroup;
children = (
259D983B154F6C90008C90F5 /* benchmark_parents_and_children.json */,
252EFB2714DA0689004863C8 /* NakedEvents.json */,
25160FD11456F2330060A5C5 /* ArrayOfNestedDictionaries.json */,
25160FD21456F2330060A5C5 /* ArrayOfResults.json */,
@@ -2234,6 +2264,8 @@
25079C6F151B93DB00266AE7 /* NSEntityDescription+RKAdditions.h in Headers */,
252A202D153471380078F8AD /* NSArray+RKAdditions.h in Headers */,
259D9847154F8744008C90F5 /* RKBenchmark.h in Headers */,
259D98541550C69A008C90F5 /* RKEntityByAttributeCache.h in Headers */,
259D985E155218E5008C90F5 /* RKEntityCache.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2356,6 +2388,8 @@
25079C70151B93DB00266AE7 /* NSEntityDescription+RKAdditions.h in Headers */,
252A20311534714D0078F8AD /* NSArray+RKAdditions.h in Headers */,
259D9848154F8744008C90F5 /* RKBenchmark.h in Headers */,
259D98551550C69A008C90F5 /* RKEntityByAttributeCache.h in Headers */,
259D985F155218E5008C90F5 /* RKEntityCache.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2526,6 +2560,7 @@
252EFB2814DA0689004863C8 /* NakedEvents.json in Resources */,
25CAAA9415254E7800CAE5D7 /* ArrayOfHumans.json in Resources */,
25119FB6154A34B400C6BC58 /* parents_and_children.json in Resources */,
259D983C154F6C90008C90F5 /* benchmark_parents_and_children.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2587,6 +2622,7 @@
252EFB2914DA0689004863C8 /* NakedEvents.json in Resources */,
25CAAA9515254E7800CAE5D7 /* ArrayOfHumans.json in Resources */,
25119FB7154A34B400C6BC58 /* parents_and_children.json in Resources */,
259D983D154F6C90008C90F5 /* benchmark_parents_and_children.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2759,6 +2795,8 @@
25079C71151B93DB00266AE7 /* NSEntityDescription+RKAdditions.m in Sources */,
252A202E153471380078F8AD /* NSArray+RKAdditions.m in Sources */,
259D9849154F8744008C90F5 /* RKBenchmark.m in Sources */,
259D98561550C69A008C90F5 /* RKEntityByAttributeCache.m in Sources */,
259D9860155218E5008C90F5 /* RKEntityCache.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2845,6 +2883,8 @@
252A2034153477870078F8AD /* NSArray+RKAdditionsTest.m in Sources */,
2501405315366000004E0466 /* RKObjectiveCppTest.mm in Sources */,
25A2476E153E667E003240B6 /* RKCacheTest.m in Sources */,
259D985A1550C6BE008C90F5 /* RKInMemoryEntityAttributeCacheTest.m in Sources */,
259D986415521B20008C90F5 /* RKEntityCacheTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2944,6 +2984,8 @@
250B849E152B6F63002581F9 /* RKObjectMappingProvider+CoreData.m in Sources */,
252A2030153471470078F8AD /* NSArray+RKAdditions.m in Sources */,
259D984A154F8744008C90F5 /* RKBenchmark.m in Sources */,
259D98571550C69A008C90F5 /* RKEntityByAttributeCache.m in Sources */,
259D9861155218E5008C90F5 /* RKEntityCache.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3030,6 +3072,8 @@
252A2035153477870078F8AD /* NSArray+RKAdditionsTest.m in Sources */,
2501405415366000004E0466 /* RKObjectiveCppTest.mm in Sources */,
25A2476F153E667E003240B6 /* RKCacheTest.m in Sources */,
259D985B1550C6BE008C90F5 /* RKInMemoryEntityAttributeCacheTest.m in Sources */,
259D986515521B20008C90F5 /* RKEntityCacheTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,155 @@
//
// RKEntityCacheTest.m
// RestKit
//
// Created by Blake Watters on 5/2/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import "RKTestEnvironment.h"
#import "NSEntityDescription+RKAdditions.h"
#import "RKEntityCache.h"
#import "RKEntityByAttributeCache.h"
#import "RKHuman.h"
@interface RKEntityCacheTest : RKTestCase
@property (nonatomic, retain) RKManagedObjectStore *objectStore;
@property (nonatomic, retain) RKEntityCache *cache;
@property (nonatomic, retain) NSEntityDescription *entity;
@end
@implementation RKEntityCacheTest
@synthesize objectStore = _objectStore;
@synthesize cache = _cache;
@synthesize entity = _entity;
- (void)setUp
{
[RKTestFactory setUp];
self.objectStore = [RKTestFactory managedObjectStore];
_cache = [[RKEntityCache alloc] initWithManagedObjectContext:self.objectStore.primaryManagedObjectContext];
self.entity = [RKHuman entityDescriptionInContext:self.objectStore.primaryManagedObjectContext];
}
- (void)tearDown
{
self.objectStore = nil;
self.cache = nil;
[RKTestFactory tearDown];
}
- (void)testInitializationSetsManagedObjectContext
{
assertThat(_cache.managedObjectContext, is(equalTo(self.objectStore.primaryManagedObjectContext)));
}
- (void)testIsEntityCachedByAttribute
{
assertThatBool([_cache isEntity:self.entity cachedByAttribute:@"railsID"], is(equalToBool(NO)));
[_cache cacheObjectsForEntity:self.entity byAttribute:@"railsID"];
assertThatBool([_cache isEntity:self.entity cachedByAttribute:@"railsID"], is(equalToBool(YES)));
}
- (void)testRetrievalOfUnderlyingEntityAttributeCache
{
[_cache cacheObjectsForEntity:self.entity byAttribute:@"railsID"];
RKEntityByAttributeCache *attributeCache = [_cache attributeCacheForEntity:self.entity attribute:@"railsID"];
assertThat(attributeCache, is(notNilValue()));
}
- (void)testRetrievalOfUnderlyingEntityAttributeCaches
{
[_cache cacheObjectsForEntity:self.entity byAttribute:@"railsID"];
NSSet *caches = [_cache attributeCachesForEntity:self.entity];
assertThat(caches, is(notNilValue()));
assertThatInteger([caches count], is(equalToInteger(1)));
}
- (void)testRetrievalOfObjectForEntityWithAttributeValue
{
RKHuman *human = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human.railsID = [NSNumber numberWithInteger:12345];
NSError *error = nil;
[self.objectStore save:&error];
[_cache cacheObjectsForEntity:self.entity byAttribute:@"railsID"];
NSManagedObject *fetchedObject = [self.cache objectForEntity:self.entity withAttribute:@"railsID" value:[NSNumber numberWithInteger:12345]];
assertThat(fetchedObject, is(notNilValue()));
}
- (void)testRetrievalOfObjectsForEntityWithAttributeValue
{
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
RKHuman *human2 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human2.railsID = [NSNumber numberWithInteger:12345];
NSError *error = nil;
[self.objectStore save:&error];
[_cache cacheObjectsForEntity:self.entity byAttribute:@"railsID"];
NSSet *objects = [self.cache objectsForEntity:self.entity withAttribute:@"railsID" value:[NSNumber numberWithInteger:12345]];
assertThat(objects, hasCountOf(2));
assertThat(objects, containsInAnyOrder(human1, human2, nil));
}
- (void)testThatFlushEmptiesAllUnderlyingAttributeCaches
{
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
human1.name = @"Blake";
RKHuman *human2 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human2.railsID = [NSNumber numberWithInteger:12345];
human2.name = @"Sarah";
[self.objectStore save:nil];
[_cache cacheObjectsForEntity:self.entity byAttribute:@"railsID"];
[_cache cacheObjectsForEntity:self.entity byAttribute:@"name"];
NSSet *objects = [self.cache objectsForEntity:self.entity withAttribute:@"railsID" value:[NSNumber numberWithInteger:12345]];
assertThat(objects, hasCountOf(2));
assertThat(objects, containsInAnyOrder(human1, human2, nil));
objects = [self.cache objectsForEntity:self.entity withAttribute:@"name" value:@"Blake"];
assertThat(objects, hasCountOf(1));
assertThat(objects, contains(human1, nil));
[self.cache flush];
objects = [self.cache objectsForEntity:self.entity withAttribute:@"railsID" value:[NSNumber numberWithInteger:12345]];
assertThat(objects, is(empty()));
objects = [self.cache objectsForEntity:self.entity withAttribute:@"name" value:@"Blake"];
assertThat(objects, is(empty()));
}
- (void)testAddingObjectAddsToEachUnderlyingEntityAttributeCaches
{
[_cache cacheObjectsForEntity:self.entity byAttribute:@"railsID"];
[_cache cacheObjectsForEntity:self.entity byAttribute:@"name"];
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
human1.name = @"Blake";
RKHuman *human2 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human2.railsID = [NSNumber numberWithInteger:12345];
human2.name = @"Sarah";
[_cache addObject:human1];
[_cache addObject:human2];
NSSet *objects = [self.cache objectsForEntity:self.entity withAttribute:@"railsID" value:[NSNumber numberWithInteger:12345]];
assertThat(objects, hasCountOf(2));
assertThat(objects, containsInAnyOrder(human1, human2, nil));
objects = [self.cache objectsForEntity:self.entity withAttribute:@"name" value:@"Blake"];
assertThat(objects, hasCountOf(1));
assertThat(objects, contains(human1, nil));
}
- (void)testRemovingObjectRemovesFromUnderlyingEntityAttributeCaches
{
}
@end

View File

@@ -0,0 +1,13 @@
//
// RKInMemoryEntityAttributeCacheTest.h
// RestKit
//
// Created by Blake Watters on 5/1/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface RKInMemoryEntityAttributeCacheTest : NSObject
@end

View File

@@ -0,0 +1,345 @@
//
// RKInMemoryEntityAttributeCacheTest.m
// RestKit
//
// Created by Blake Watters on 5/1/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import "RKTestEnvironment.h"
#import "NSEntityDescription+RKAdditions.h"
#import "RKEntityByAttributeCache.h"
#import "RKHuman.h"
#import "RKChild.h"
@interface RKInMemoryEntityAttributeCacheTest : RKTestCase
@property (nonatomic, retain) RKManagedObjectStore *objectStore;
@property (nonatomic, retain) RKEntityByAttributeCache *cache;
@end
@implementation RKInMemoryEntityAttributeCacheTest
@synthesize objectStore = _objectStore;
@synthesize cache = _cache;
- (void)setUp
{
[RKTestFactory setUp];
self.objectStore = [RKTestFactory managedObjectStore];
NSEntityDescription *entity = [RKHuman entityDescriptionInContext:self.objectStore.primaryManagedObjectContext];
self.cache = [[RKEntityByAttributeCache alloc] initWithEntity:entity
attribute:@"railsID"
managedObjectContext:self.objectStore.primaryManagedObjectContext];
// Disable cache monitoring. Tested in specific cases.
self.cache.monitorsContextForChanges = NO;
}
- (void)tearDown
{
self.objectStore = nil;
[RKTestFactory tearDown];
}
#pragma mark - Identity Tests
- (void)testEntityIsAssigned
{
NSEntityDescription *entity = [RKHuman entityDescriptionInContext:self.objectStore.primaryManagedObjectContext];
assertThat(self.cache.entity, is(equalTo(entity)));
}
- (void)testManagedObjectContextIsAssigned
{
NSManagedObjectContext *context = self.objectStore.primaryManagedObjectContext;
assertThat(self.cache.managedObjectContext, is(equalTo(context)));
}
- (void)testAttributeNameIsAssigned
{
assertThat(self.cache.attribute, is(equalTo(@"railsID")));
}
#pragma mark - Loading and Flushing
- (void)testLoadSetsLoadedToYes
{
[self.cache load];
assertThatBool(self.cache.isLoaded, is(equalToBool(YES)));
}
- (void)testLoadSetsCountAppropriately
{
RKHuman *human = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human.railsID = [NSNumber numberWithInteger:12345];
NSError *error = nil;
[self.objectStore save:&error];
assertThat(error, is(nilValue()));
[self.cache load];
assertThatInteger([self.cache count], is(equalToInteger(1)));
}
- (void)testFlushCacheRemovesObjects
{
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
RKHuman *human2 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human2.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore save:nil];
[self.cache addObject:human1];
[self.cache addObject:human2];
[self.cache flush];
assertThatBool([self.cache containsObject:human1], is(equalToBool(NO)));
assertThatBool([self.cache containsObject:human2], is(equalToBool(NO)));
}
- (void)testFlushCacheReturnsCountToZero
{
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
RKHuman *human2 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human2.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore save:nil];
[self.cache addObject:human1];
[self.cache addObject:human2];
[self.cache flush];
assertThatInteger([self.cache count], is(equalToInteger(0)));
}
#pragma mark - Retrieving Objects
- (void)testRetrievalByNumericValue
{
RKHuman *human = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore save:nil];
[self.cache load];
NSManagedObject *object = [self.cache objectWithAttributeValue:[NSNumber numberWithInteger:12345]];
assertThat(object, is(equalTo(human)));
}
- (void)testRetrievalOfNumericPropertyByStringValue
{
RKHuman *human = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore save:nil];
[self.cache load];
NSManagedObject *object = [self.cache objectWithAttributeValue:@"12345"];
assertThat(object, is(equalTo(human)));
}
- (void)testRetrievalOfObjectsWithAttributeValue
{
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
RKHuman *human2 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human2.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore save:nil];
[self.cache addObject:human1];
[self.cache addObject:human2];
NSSet *set = [self.cache objectsWithAttributeValue:[NSNumber numberWithInt:12345]];
assertThat(set, hasCountOf(2));
assertThat([set anyObject], is(instanceOf([NSManagedObject class])));
}
- (void)testAddingObjectToCache
{
RKHuman *human = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore save:nil];
[self.cache addObject:human];
assertThatBool([self.cache containsObject:human], is(equalToBool(YES)));
}
- (void)testAddingObjectWithDuplicateAttributeValue
{
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
RKHuman *human2 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human2.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore save:nil];
[self.cache addObject:human1];
[self.cache addObject:human2];
assertThatBool([self.cache containsObject:human1], is(equalToBool(YES)));
assertThatBool([self.cache containsObject:human2], is(equalToBool(YES)));
}
- (void)testRemovingObjectFromCache
{
RKHuman *human = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore save:nil];
[self.cache addObject:human];
assertThatBool([self.cache containsObject:human], is(equalToBool(YES)));
[self.cache removeObject:human];
assertThatBool([self.cache containsObject:human], is(equalToBool(NO)));
}
- (void)testRemovingObjectWithExistingAttributeValue
{
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
RKHuman *human2 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human2.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore save:nil];
[self.cache addObject:human1];
[self.cache addObject:human2];
assertThatBool([self.cache containsObject:human1], is(equalToBool(YES)));
assertThatBool([self.cache containsObject:human2], is(equalToBool(YES)));
[self.cache removeObject:human1];
assertThatBool([self.cache containsObject:human1], is(equalToBool(NO)));
assertThatBool([self.cache containsObject:human2], is(equalToBool(YES)));
}
#pragma mark - Inspecting Cache State
- (void)testContainsObjectReturnsNoForDifferingEntities
{
NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKCloud" inManagedObjectContext:self.objectStore.primaryManagedObjectContext];
NSManagedObject *cloud = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.objectStore.primaryManagedObjectContext];
assertThatBool([self.cache containsObject:cloud], is(equalToBool(NO)));
}
- (void)testContainsObjectReturnsNoForSubEntities
{
RKHuman *human = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human.railsID = [NSNumber numberWithInteger:12345];
RKChild *child = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
child.railsID = [NSNumber numberWithInteger:12345];
[self.cache addObject:human];
assertThatBool([self.cache containsObject:human], is(equalToBool(YES)));
assertThatBool([self.cache containsObject:child], is(equalToBool(NO)));
}
- (void)testContainsObjectWithAttributeValue
{
RKHuman *human = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore save:nil];
[self.cache addObject:human];
assertThatBool([self.cache containsObjectWithAttributeValue:[NSNumber numberWithInteger:12345]], is(equalToBool(YES)));
}
- (void)testCountWithAttributeValue
{
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
RKHuman *human2 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human2.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore save:nil];
[self.cache addObject:human1];
[self.cache addObject:human2];
assertThatInteger([self.cache countWithAttributeValue:[NSNumber numberWithInteger:12345]], is(equalToInteger(2)));
}
- (void)testThatUnloadedCacheReturnsCountOfZero
{
assertThatInteger([self.cache count], is(equalToInteger(0)));
}
#pragma mark - Lifecycle Events
- (void)testManagedObjectContextProcessPendingChangesAddsNewObjectsToCache
{
self.cache.monitorsContextForChanges = YES;
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore.primaryManagedObjectContext processPendingChanges];
assertThatBool([self.cache containsObject:human1], is(equalToBool(YES)));
}
- (void)testManagedObjectContextProcessPendingChangesIgnoresObjectsOfDifferentEntityTypes
{
self.cache.monitorsContextForChanges = YES;
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKCloud" inManagedObjectContext:self.objectStore.primaryManagedObjectContext];
NSManagedObject *cloud = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.objectStore.primaryManagedObjectContext];
[cloud setValue:@"Cumulus" forKey:@"name"];
[self.objectStore.primaryManagedObjectContext processPendingChanges];
assertThatBool([self.cache containsObject:human1], is(equalToBool(YES)));
assertThatBool([self.cache containsObject:cloud], is(equalToBool(NO)));
}
- (void)testManagedObjectContextProcessPendingChangesAddsUpdatedObjectsToCache
{
self.cache.monitorsContextForChanges = YES;
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore.primaryManagedObjectContext processPendingChanges];
assertThatBool([self.cache containsObject:human1], is(equalToBool(YES)));
[self.cache removeObject:human1];
human1.name = @"Modified Name";
assertThatBool([self.cache containsObject:human1], is(equalToBool(NO)));
[self.objectStore.primaryManagedObjectContext processPendingChanges];
assertThatBool([self.cache containsObject:human1], is(equalToBool(YES)));
}
- (void)testManagedObjectContextProcessPendingChangesRemovesExistingObjectsFromCache
{
self.cache.monitorsContextForChanges = YES;
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore.primaryManagedObjectContext processPendingChanges];
assertThatBool([self.cache containsObject:human1], is(equalToBool(YES)));
[self.objectStore.primaryManagedObjectContext deleteObject:human1];
[self.objectStore.primaryManagedObjectContext processPendingChanges];
assertThatBool([self.cache containsObject:human1], is(equalToBool(NO)));
}
#if TARGET_OS_IPHONE
- (void)testCacheIsFlushedOnMemoryWarning
{
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
RKHuman *human2 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human2.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore save:nil];
[self.cache addObject:human1];
[self.cache addObject:human2];
assertThatBool([self.cache containsObject:human1], is(equalToBool(YES)));
assertThatBool([self.cache containsObject:human2], is(equalToBool(YES)));
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidReceiveMemoryWarningNotification object:self];
}
#endif
- (void)testCreatingProcessingAndDeletingObjectsWorksAsExpected {
self.cache.monitorsContextForChanges = YES;
RKHuman *human1 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human1.railsID = [NSNumber numberWithInteger:12345];
RKHuman *human2 = [RKHuman createInContext:self.objectStore.primaryManagedObjectContext];
human2.railsID = [NSNumber numberWithInteger:12345];
[self.objectStore.primaryManagedObjectContext processPendingChanges];
assertThatBool([self.cache containsObject:human1], is(equalToBool(YES)));
assertThatBool([self.cache containsObject:human2], is(equalToBool(YES)));
[self.objectStore.primaryManagedObjectContext deleteObject:human2];
// Save and reload the cache. This will result in the cached temporary
// object ID's being released during the cache flush.
[self.objectStore.primaryManagedObjectContext save:nil];
[self.cache load];
assertThatBool([self.cache containsObject:human1], is(equalToBool(YES)));
assertThatBool([self.cache containsObject:human2], is(equalToBool(NO)));
}
@end

View File

@@ -19,6 +19,7 @@
//
#import "RKTestEnvironment.h"
#import "RKInMemoryEntityCache.h"
#import "RKHuman.h"
@interface RKInMemoryEntityCache ()
@@ -262,6 +263,7 @@
}
- (void)testThatRepeatedInvocationsOfLoadObjectDoesNotDuplicateObjects {
RKLogConfigureByName("RestKit/CoreData", RKLogLevelTrace);
RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore];
objectStore.cacheStrategy = [RKInMemoryManagedObjectCache new];
RKObjectManager *objectManager = [RKTestFactory objectManager];
@@ -274,12 +276,12 @@
for (NSUInteger i = 0; i < 5; i++) {
RKTestResponseLoader *responseLoader = [RKTestResponseLoader responseLoader];
responseLoader.timeout = 1000;
[objectManager loadObjectsAtResourcePath:@"/JSON/ArrayOfHumans.json" delegate:responseLoader];
[responseLoader waitForResponse];
for (RKHuman *object in [RKHuman allObjects]) {
if ([object.railsID intValue] == 201) {
[objectStore.managedObjectContextForCurrentThread deleteObject:object];
[objectStore.managedObjectContextForCurrentThread save:nil];
}
}

View File

@@ -25,6 +25,7 @@
#import "RKHuman.h"
#import "RKChild.h"
#import "RKParent.h"
#import "RKBenchmark.h"
@interface RKManagedObjectMappingOperationTest : RKTestCase {
@@ -352,6 +353,7 @@
// NOTE: This may be fragile. Reverse order seems to trigger them to be mapped parent first. NSDictionary
// keys are not guaranteed to return in any particular order
[mappingProvider setMapping:parentMapping forKeyPath:@"parents"];
[mappingProvider setMapping:childMapping forKeyPath:@"children"];
NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"ConnectingParents.json"];
RKObjectMapper *mapper = [RKObjectMapper mapperWithObject:JSON mappingProvider:mappingProvider];
@@ -418,4 +420,74 @@
assertThatInteger(childrenCount, is(equalToInteger(4)));
}
- (void)testMappingAPayloadContainingRepeatedObjectsPerformsAcceptablyWithFetchRequestMappingCache {
RKManagedObjectStore *store = [RKTestFactory managedObjectStore];
store.cacheStrategy = [RKFetchRequestManagedObjectCache new];
RKManagedObjectMapping* childMapping = [RKManagedObjectMapping mappingForClass:[RKChild class] inManagedObjectStore:store];
childMapping.primaryKeyAttribute = @"childID";
[childMapping mapAttributes:@"name", @"childID", nil];
RKManagedObjectMapping* parentMapping = [RKManagedObjectMapping mappingForClass:[RKParent class] inManagedObjectStore:store];
[parentMapping mapAttributes:@"parentID", @"name", nil];
parentMapping.primaryKeyAttribute = @"parentID";
[parentMapping mapRelationship:@"children" withMapping:childMapping];
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider new];
// NOTE: This may be fragile. Reverse order seems to trigger them to be mapped parent first. NSDictionary
// keys are not guaranteed to return in any particular order
[mappingProvider setObjectMapping:parentMapping forKeyPath:@"parents"];
NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"benchmark_parents_and_children.json"];
RKObjectMapper *mapper = [RKObjectMapper mapperWithObject:JSON mappingProvider:mappingProvider];
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelOff);
RKLogConfigureByName("RestKit/CoreData", RKLogLevelOff);
[RKBenchmark report:@"Mapping with Fetch Request Cache" executionBlock:^{
for (NSUInteger i=0; i<50; i++) {
[mapper performMapping];
}
}];
NSUInteger parentCount = [RKParent count:nil];
NSUInteger childrenCount = [RKChild count:nil];
assertThatInteger(parentCount, is(equalToInteger(25)));
assertThatInteger(childrenCount, is(equalToInteger(51)));
}
- (void)testMappingAPayloadContainingRepeatedObjectsPerformsAcceptablyWithInMemoryMappingCache {
RKManagedObjectStore *store = [RKTestFactory managedObjectStore];
store.cacheStrategy = [RKInMemoryManagedObjectCache new];
RKManagedObjectMapping* childMapping = [RKManagedObjectMapping mappingForClass:[RKChild class] inManagedObjectStore:store];
childMapping.primaryKeyAttribute = @"childID";
[childMapping mapAttributes:@"name", @"childID", nil];
RKManagedObjectMapping* parentMapping = [RKManagedObjectMapping mappingForClass:[RKParent class] inManagedObjectStore:store];
[parentMapping mapAttributes:@"parentID", @"name", nil];
parentMapping.primaryKeyAttribute = @"parentID";
[parentMapping mapRelationship:@"children" withMapping:childMapping];
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider new];
// NOTE: This may be fragile. Reverse order seems to trigger them to be mapped parent first. NSDictionary
// keys are not guaranteed to return in any particular order
[mappingProvider setObjectMapping:parentMapping forKeyPath:@"parents"];
NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"benchmark_parents_and_children.json"];
RKObjectMapper *mapper = [RKObjectMapper mapperWithObject:JSON mappingProvider:mappingProvider];
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelOff);
RKLogConfigureByName("RestKit/CoreData", RKLogLevelOff);
[RKBenchmark report:@"Mapping with In Memory Cache" executionBlock:^{
for (NSUInteger i=0; i<50; i++) {
[mapper performMapping];
}
}];
NSUInteger parentCount = [RKParent count:nil];
NSUInteger childrenCount = [RKChild count:nil];
assertThatInteger(parentCount, is(equalToInteger(25)));
assertThatInteger(childrenCount, is(equalToInteger(51)));
}
@end

View File

@@ -32,6 +32,16 @@
@implementation RKManagedObjectMappingTest
- (void)setUp
{
[RKTestFactory setUp];
}
- (void)tearDown
{
[RKTestFactory tearDown];
}
- (void)testShouldReturnTheDefaultValueForACoreDataAttribute {
// Load Core Data
RKManagedObjectStore *store = [RKTestFactory managedObjectStore];
@@ -220,13 +230,14 @@
mapping.primaryKeyAttribute = @"railsID";
[mapping addAttributeMapping:[RKObjectAttributeMapping mappingFromKeyPath:@"id" toKeyPath:@"railsID"]];
RKHuman* human = [RKHuman object];
RKHuman* human = [RKHuman createInContext:store.primaryManagedObjectContext];
human.railsID = [NSNumber numberWithInt:123];
[store save:nil];
assertThatBool([RKHuman hasAtLeastOneEntity], is(equalToBool(YES)));
NSDictionary* data = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:123] forKey:@"id"];
id object = [mapping mappableObjectForData:data];
NSManagedObject *object = [mapping mappableObjectForData:data];
assertThat([object managedObjectContext], is(equalTo(store.primaryManagedObjectContext)));
assertThat(object, isNot(nilValue()));
assertThat(object, is(equalTo(human)));
}