Skip to content

Commit

Permalink
Integrated primaryKey extension to NSEntityDescription and refactored…
Browse files Browse the repository at this point in the history
… cache strategy

classes to eliminate issues with duplicated objects. closes #611, #612, #613, #618

* NSEntityDescription is now aware of the primaryKeyAttribute. Can be configured via
Interface Builder within Xcode or programatically.
* Added findByPrimaryKey: interface to the Core Data extensions.
* Relaxed dependencies on RKManagedObjectMapping across the system now that primaryKey is
available without a reference to the mapping.
  • Loading branch information
blakewatters committed Apr 4, 2012
1 parent 1b324cc commit a545c39
Show file tree
Hide file tree
Showing 32 changed files with 647 additions and 268 deletions.
5 changes: 3 additions & 2 deletions Code/CoreData/CoreData.h
Expand Up @@ -26,12 +26,13 @@
#import "RKManagedObjectMapping.h"
#import "RKManagedObjectMappingOperation.h"
#import "RKManagedObjectMappingCache.h"
#import "RKInMemoryMappingCache.h"
#import "RKFetchRequestMappingCache.h"
#import "RKInMemoryManagedObjectCache.h"
#import "RKFetchRequestManagedObjectCache.h"
#import "RKSearchableManagedObject.h"
#import "RKSearchWord.h"

#import "RKObjectPropertyInspector+CoreData.h"
#import "RKObjectMappingProvider+CoreData.h"
#import "NSManagedObjectContext+RKAdditions.h"
#import "NSManagedObject+RKAdditions.h"
#import "NSEntityDescription+RKAdditions.h"
39 changes: 39 additions & 0 deletions Code/CoreData/NSEntityDescription+RKAdditions.h
@@ -0,0 +1,39 @@
//
// NSEntityDescription+RKAdditions.h
// RestKit
//
// Created by Blake Watters on 3/22/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//

#import <CoreData/CoreData.h>

/**
The key for retrieving the name of the attribute that acts as
the primary key from the user info dictionary of the receiving NSEntityDescription.
**Value**: @"primaryKeyAttribute"
*/
extern NSString * const RKEntityDescriptionPrimaryKeyAttributeUserInfoKey;

/**
Provides extensions to NSEntityDescription for various common tasks.
*/
@interface NSEntityDescription (RKAdditions)

/**
The name of the attribute that acts as the primary key for the receiver.
The primary key attribute can be configured in two ways:
1. From within the Xcode Core Data editing view by
adding the desired attribute's name as the value for the
key `primaryKeyAttribute` to the user info dictionary.
1. Programmatically, by retrieving the NSEntityDescription instance and
setting the property's value.
Programmatically configured values take precedence over the user info
dictionary.
*/
@property(nonatomic, retain) NSString *primaryKeyAttribute;

@end
38 changes: 38 additions & 0 deletions Code/CoreData/NSEntityDescription+RKAdditions.m
@@ -0,0 +1,38 @@
//
// NSEntityDescription+RKAdditions.m
// RestKit
//
// Created by Blake Watters on 3/22/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//

#import <objc/runtime.h>
#import "NSEntityDescription+RKAdditions.h"

NSString * const RKEntityDescriptionPrimaryKeyAttributeUserInfoKey = @"primaryKeyAttribute";
static char primaryKeyAttributeKey;

@implementation NSEntityDescription (RKAdditions)

- (NSString *)primaryKeyAttribute
{
// Check for an associative object reference
NSString *primaryKeyAttribute = (NSString *) objc_getAssociatedObject(self, &primaryKeyAttributeKey);

// Fall back to the userInfo dictionary
if (! primaryKeyAttribute) {
primaryKeyAttribute = [self.userInfo valueForKey:RKEntityDescriptionPrimaryKeyAttributeUserInfoKey];
}

return primaryKeyAttribute;
}

- (void)setPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
{
objc_setAssociatedObject(self,
&primaryKeyAttributeKey,
primaryKeyAttribute,
OBJC_ASSOCIATION_RETAIN);
}

@end
22 changes: 21 additions & 1 deletion Code/CoreData/NSManagedObject+ActiveRecord.h
Expand Up @@ -22,7 +22,8 @@
@end

/**
Extensions for NSManage
Provides extensions to NSManagedObject implementing a low-ceremony querying
interface.
*/
@interface NSManagedObject (ActiveRecord)

Expand Down Expand Up @@ -102,6 +103,25 @@
*/
- (BOOL)isNew;

/**
Finds the instance of the receiver's entity with the given value for the primary key attribute
in the managed object context for the current thread.
@param primaryKeyValue The value for the receiving entity's primary key attribute.
@return The object with the primary key attribute equal to the given value or nil.
*/
+ (id)findByPrimaryKey:(id)primaryKeyValue;

/**
Finds the instance of the receiver's entity with the given value for the primary key attribute in
the given managed object context.
@param primaryKeyValue The value for the receiving entity's primary key attribute.
@param context The managed object context to find the instance in.
@return The object with the primary key attribute equal to the given value or nil.
*/
+ (id)findByPrimaryKey:(id)primaryKeyValue inContext:(NSManagedObjectContext *)context;

////////////////////////////////////////////////////////////////////////////////////////////////////

+ (NSManagedObjectContext*)currentContext;
Expand Down
16 changes: 16 additions & 0 deletions Code/CoreData/NSManagedObject+ActiveRecord.m
Expand Up @@ -13,6 +13,7 @@
#import "RKManagedObjectStore.h"
#import "RKLog.h"
#import "RKFixCategoryBug.h"
#import "NSEntityDescription+RKAdditions.h"

// Set Logging Component
#undef RKLogComponent
Expand Down Expand Up @@ -136,6 +137,21 @@ - (BOOL)isNew {
return [vals count] == 0;
}

+ (id)findByPrimaryKey:(id)primaryKeyValue inContext:(NSManagedObjectContext *)context {
NSEntityDescription *entity = [self entityDescriptionInContext:context];
NSString *primaryKeyAttribute = entity.primaryKeyAttribute;
if (! primaryKeyAttribute) {
RKLogWarning(@"Attempt to findByPrimaryKey for entity with nil primaryKeyAttribute. Set the primaryKeyAttribute and try again! %@", entity);
return nil;
}

return [self findFirstByAttribute:primaryKeyAttribute withValue:primaryKeyValue inContext:context];
}

+ (id)findByPrimaryKey:(id)primaryKeyValue {
return [self findByPrimaryKey:primaryKeyValue inContext:[NSManagedObjectContext contextForCurrentThread]];
}

#pragma mark - MagicalRecord Ported Methods

+ (NSManagedObjectContext*)currentContext; {
Expand Down
3 changes: 3 additions & 0 deletions Code/CoreData/NSManagedObject+RKAdditions.h
Expand Up @@ -10,6 +10,9 @@

@class RKManagedObjectStore, RKManagedObjectMapping;

/**
Provides extensions to NSManagedObject for various common tasks.
*/
@interface NSManagedObject (RKAdditions)

/**
Expand Down
3 changes: 3 additions & 0 deletions Code/CoreData/NSManagedObjectContext+RKAdditions.h
Expand Up @@ -10,6 +10,9 @@

@class RKManagedObjectStore;

/**
Provides extensions to NSManagedObjectContext for various common tasks.
*/
@interface NSManagedObjectContext (RKAdditions)

/**
Expand Down
19 changes: 19 additions & 0 deletions Code/CoreData/RKFetchRequestManagedObjectCache.h
@@ -0,0 +1,19 @@
//
// RKFetchRequestManagedObjectCache.h
// RestKit
//
// Created by Jeff Arena on 1/24/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//

#import "RKManagedObjectMappingCache.h"

/**
Provides a simple managed object cache strategy in which every request for an object
is satisfied by dispatching an NSFetchRequest against the Core Data persistent store.
Performance can be disappointing for data sets with a large amount of redundant data
being mapped and connected together, but the memory footprint stays flat.
*/
@interface RKFetchRequestManagedObjectCache : NSObject <RKManagedObjectCacheing>

@end
Expand Up @@ -6,30 +6,31 @@
// Copyright (c) 2012 RestKit. All rights reserved.
//

#import "RKFetchRequestMappingCache.h"
#import "RKFetchRequestManagedObjectCache.h"
#import "NSManagedObject+ActiveRecord.h"
#import "NSEntityDescription+RKAdditions.h"
#import "RKLog.h"

// Set Logging Component
#undef RKLogComponent
#define RKLogComponent lcl_cRestKitCoreData

@implementation RKFetchRequestMappingCache
@implementation RKFetchRequestManagedObjectCache

- (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
withMapping:(RKManagedObjectMapping *)mapping
andPrimaryKeyValue:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext {
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
NSAssert(entity, @"Cannot find existing managed object without a target class");
NSAssert(mapping, @"Cannot find existing managed object instance without mapping");
NSAssert(mapping.primaryKeyAttribute, @"Cannot find existing managed object instance without mapping that defines a primaryKeyAttribute");
NSAssert(primaryKeyAttribute, @"Cannot find existing managed object instance without mapping that defines a primaryKeyAttribute");
NSAssert(primaryKeyValue, @"Cannot find existing managed object by primary key without a value");
NSAssert(managedObjectContext, @"Cannot find existing managed object with a context");

NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setFetchLimit:1];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"%K = %@", mapping.primaryKeyAttribute, primaryKeyValue]];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"%K = %@", primaryKeyAttribute, primaryKeyValue]];
NSArray *objects = [NSManagedObject executeFetchRequest:fetchRequest];
RKLogDebug(@"Found objects '%@' using fetchRequest '%@'", objects, fetchRequest);

Expand Down
13 changes: 0 additions & 13 deletions Code/CoreData/RKFetchRequestMappingCache.h

This file was deleted.

59 changes: 37 additions & 22 deletions Code/CoreData/RKInMemoryEntityCache.h
Expand Up @@ -7,62 +7,77 @@
//

#import <CoreData/CoreData.h>
#import "RKManagedObjectMapping.h"

@interface RKInMemoryEntityCache : NSObject {
NSMutableDictionary *_entityCache;
}
/**
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.
@property (nonatomic, readonly) NSDictionary *entityCache;
@param context The managed object context containing objects to be cached.
@returns self, initialized with context.
*/
- (id)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;

/// @name Cacheing Objects by Attribute

/**
Retrieves all objects within the cache
*/
- (NSMutableDictionary *)cachedObjectsForEntity:(NSEntityDescription *)entity
withMapping:(RKManagedObjectMapping *)mapping
byAttribute:(NSString *)attributeName
inContext:(NSManagedObjectContext *)managedObjectContext;

/**
*/
- (NSManagedObject *)cachedObjectForEntity:(NSEntityDescription *)entity
withMapping:(RKManagedObjectMapping *)mapping
andPrimaryKeyValue:(id)primaryKeyValue
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
withMapping:(RKManagedObjectMapping *)mapping
byAttribute:(NSString *)attributeName
inContext:(NSManagedObjectContext *)managedObjectContext;

/**
*/
- (void)cacheObject:(NSManagedObject *)managedObject
withMapping:(RKManagedObjectMapping *)mapping
byAttribute:(NSString *)attributeName
inContext:(NSManagedObjectContext *)managedObjectContext;

/**
*/
- (void)cacheObject:(NSEntityDescription *)entity
withMapping:(RKManagedObjectMapping *)mapping
andPrimaryKeyValue:(id)primaryKeyValue
byAttribute:(NSString *)attributeName
value:(id)primaryKeyValue
inContext:(NSManagedObjectContext *)managedObjectContext;

/**
*/
- (void)expireCacheEntryForObject:(NSManagedObject *)managedObject
withMapping:(RKManagedObjectMapping *)mapping
byAttribute:(NSString *)attributeName
inContext:(NSManagedObjectContext *)managedObjectContext;

/**
*/
- (void)expireCacheEntryForEntity:(NSEntityDescription *)entity;

/**
*/
- (BOOL)shouldCoerceAttributeToString:(NSString *)attribute forEntity:(NSEntityDescription *)entity;

/**
*/
- (NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID inContext:(NSManagedObjectContext *)managedObjectContext;
- (void)expireCacheEntriesForEntity:(NSEntityDescription *)entity;

@end

0 comments on commit a545c39

Please sign in to comment.