Skip to content

Commit

Permalink
Merge branch 'release/0.20.0-pre3'
Browse files Browse the repository at this point in the history
  • Loading branch information
blakewatters committed Dec 8, 2012
2 parents 9b78ca2 + 5c1a461 commit 1c60806
Show file tree
Hide file tree
Showing 44 changed files with 1,284 additions and 1,062 deletions.
22 changes: 22 additions & 0 deletions Code/CoreData/RKConnectionDescription.h
Expand Up @@ -46,6 +46,28 @@
Any number of attribute pairs may be specified, but all values must match for the connection to be satisfied and the relationship's value to be set.
### Connecting with Collection Values
Connections can be established by a collection of values. For example, imagine that the previously described project representation has been extended to include a list of team members who are working on the project:
{ "project":
{ "id": 12345,
"name": "My Project",
"userID": 1,
"teamMemberIDs": [1, 2, 3, 4]
}
}
The 'teamMemberIDs' contains an array specifying the ID's of the `User` objects who are collaborating on the project, which corresponds to a to-many relationship named 'teamMembers' on the `Project` entity. In this case, the 'teamMemberIDs' could be mapped on to an `NSArray` or `NSSet` property on the `Project` entity and then connected:
NSEntityDescription *projectEntity = [NSEntityDescription entityForName:@"Project" inManagedObjectContext:managedObjectContext];
NSRelationshipDescription *teamMembers = [projectEntity relationshipsByName][@"teamMembers"]; // To many relationship for the `User` entity
RKConnectionDescription *connection = [[RKConnectionDescription alloc] initWithRelationship:relationship attributes:@{ @"teamMemberIDs": @"userID" }];
When evaluating the above JSON, the connection would be established for the 'teamMembers' relationship to the `User` entities whose userID's are 1, 2, 3 or 4.
Note that collections of attribute values are always interpetted as logic OR's, but compound connections are aggregated as a logical AND. For example, if we were to add a second connecting attribute for the "gender" property and include `"gender": "male"` in the JSON, the connection would be made to all `User` managed objects whose ID is 1, 2, 3, OR 4 AND whose gender is "male".
## Key Path Connections
A key path connection is established by evaluating the key path of the connection against the managed object being connected. The returned value has type transformation applied and is then assigned to the relationship.
Expand Down
2 changes: 1 addition & 1 deletion Code/CoreData/RKEntityByAttributeCache.h
Expand Up @@ -153,7 +153,7 @@
@param context The managed object context to retrieve the objects from.
@return An array of objects with the value of attribute matching attributeValue or an empty array.
*/
- (NSArray *)objectsWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context;
- (NSSet *)objectsWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context;

///------------------------------
/// @name Managing Cached Objects
Expand Down
77 changes: 52 additions & 25 deletions Code/CoreData/RKEntityByAttributeCache.m
Expand Up @@ -27,6 +27,7 @@
#import "RKPropertyInspector.h"
#import "RKPropertyInspector+CoreData.h"
#import "NSManagedObject+RKAdditions.h"
#import "RKObjectUtilities.h"

// Set Logging Component
#undef RKLogComponent
Expand Down Expand Up @@ -54,6 +55,32 @@ static id RKCacheKeyValueForEntityAttributeWithValue(NSEntityDescription *entity
return [sortedValues componentsJoinedByString:@":"];
}

/*
This function recursively calculates a set of cache keys given a dictionary of attribute values. The basic premise is that we wish to decompose all arrays of values within the dictionary into a distinct cache key, as each object within the cache will appear for only one key.
*/
static NSArray *RKCacheKeysForEntityFromAttributeValues(NSEntityDescription *entity, NSDictionary *attributeValues)
{
NSMutableArray *cacheKeys = [NSMutableArray array];
NSSet *collectionKeys = [attributeValues keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
return RKObjectIsCollection(obj);
}];

if ([collectionKeys count] > 0) {
for (NSString *attributeName in collectionKeys) {
id attributeValue = [attributeValues objectForKey:attributeName];
for (id value in attributeValue) {
NSMutableDictionary *mutableAttributeValues = [attributeValues mutableCopy];
[mutableAttributeValues setValue:value forKey:attributeName];
[cacheKeys addObjectsFromArray:RKCacheKeysForEntityFromAttributeValues(entity, mutableAttributeValues)];
}
}
} else {
[cacheKeys addObject:RKCacheKeyForEntityWithAttributeValues(entity, attributeValues)];
}

return cacheKeys;
}

@interface RKEntityByAttributeCache ()
@property (nonatomic, strong) NSMutableDictionary *cacheKeysToObjectIDs;
@end
Expand Down Expand Up @@ -193,39 +220,39 @@ - (NSManagedObject *)objectForObjectID:(NSManagedObjectID *)objectID inContext:(

- (NSManagedObject *)objectWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context
{
NSArray *objects = [self objectsWithAttributeValues:attributeValues inContext:context];
return ([objects count] > 0) ? [objects objectAtIndex:0] : nil;
NSSet *objects = [self objectsWithAttributeValues:attributeValues inContext:context];
return ([objects count] > 0) ? [objects anyObject] : nil;
}

- (NSArray *)objectsWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context
- (NSSet *)objectsWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context
{
// TODO: Assert that the attribute values contains all of the cache attributes!!!
NSString *cacheKey = RKCacheKeyForEntityWithAttributeValues(self.entity, attributeValues);
NSArray *objectIDs = nil;
@synchronized(self.cacheKeysToObjectIDs) {
objectIDs = [[NSArray alloc] initWithArray:[self.cacheKeysToObjectIDs objectForKey:cacheKey] copyItems:YES];
}
if ([objectIDs count]) {
/**
NOTE:
In my benchmarking, retrieving the objects one at a time using existingObjectWithID: is significantly faster
than issuing a single fetch request against all object ID's.
*/
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:[objectIDs count]];
for (NSManagedObjectID *objectID in objectIDs) {
NSManagedObject *object = [self objectForObjectID:objectID inContext:context];
if (object) {
[objects addObject:object];
} else {
RKLogDebug(@"Evicting objectID association for attributes %@ of Entity '%@': %@", attributeValues, self.entity.name, objectID);
[self removeObjectID:objectID forAttributeValues:attributeValues];
NSMutableSet *objects = [NSMutableSet set];
NSArray *cacheKeys = RKCacheKeysForEntityFromAttributeValues(self.entity, attributeValues);
for (NSString *cacheKey in cacheKeys) {
NSArray *objectIDs = nil;
@synchronized(self.cacheKeysToObjectIDs) {
objectIDs = [[NSArray alloc] initWithArray:[self.cacheKeysToObjectIDs objectForKey:cacheKey] copyItems:YES];
}
if ([objectIDs count]) {
/**
NOTE:
In my benchmarking, retrieving the objects one at a time using existingObjectWithID: is significantly faster
than issuing a single fetch request against all object ID's.
*/
for (NSManagedObjectID *objectID in objectIDs) {
NSManagedObject *object = [self objectForObjectID:objectID inContext:context];
if (object) {
[objects addObject:object];
} else {
RKLogDebug(@"Evicting objectID association for attributes %@ of Entity '%@': %@", attributeValues, self.entity.name, objectID);
[self removeObjectID:objectID forAttributeValues:attributeValues];
}
}
}

return objects;
}

return [NSArray array];
return objects;
}

- (void)setObjectID:(NSManagedObjectID *)objectID forAttributeValues:(NSDictionary *)attributeValues
Expand Down
2 changes: 1 addition & 1 deletion Code/CoreData/RKEntityCache.h
Expand Up @@ -91,7 +91,7 @@
@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 withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context;
- (NSSet *)objectsForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context;

///-----------------------------------------------------------------------------
// @name Accessing Underlying Caches
Expand Down
2 changes: 1 addition & 1 deletion Code/CoreData/RKEntityCache.m
Expand Up @@ -81,7 +81,7 @@ - (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttribute
return nil;
}

- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context
- (NSSet *)objectsForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context
{
NSParameterAssert(entity);
NSParameterAssert(attributeValues);
Expand Down
15 changes: 14 additions & 1 deletion Code/CoreData/RKEntityMapping.m
Expand Up @@ -36,7 +36,7 @@

static NSArray *RKEntityIdentificationAttributesFromUserInfoOfEntity(NSEntityDescription *entity)
{
id userInfoValue = [entity userInfo][RKEntityIdentificationAttributesUserInfoKey];
id userInfoValue = [[entity userInfo] valueForKey:RKEntityIdentificationAttributesUserInfoKey];
if (userInfoValue) {
NSArray *attributeNames = [userInfoValue isKindOfClass:[NSArray class]] ? userInfoValue : @[ userInfoValue ];
NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:[attributeNames count]];
Expand Down Expand Up @@ -270,6 +270,19 @@ - (Class)classForProperty:(NSString *)propertyName
return propertyClass;
}

- (Class)classForKeyPath:(NSString *)keyPath
{
NSArray *components = [keyPath componentsSeparatedByString:@"."];
Class propertyClass = self.objectClass;
for (NSString *property in components) {
propertyClass = [[RKPropertyInspector sharedInspector] classForPropertyNamed:property ofClass:propertyClass];
if (! propertyClass) propertyClass = [[RKPropertyInspector sharedInspector] classForPropertyNamed:property ofEntity:self.entity];
if (! propertyClass) break;
}

return propertyClass;
}

+ (void)setEntityIdentificationInferenceEnabled:(BOOL)enabled
{
entityIdentificationInferenceEnabled = enabled;
Expand Down
27 changes: 18 additions & 9 deletions Code/CoreData/RKFetchRequestManagedObjectCache.m
Expand Up @@ -10,24 +10,33 @@
#import "RKLog.h"
#import "RKPropertyInspector.h"
#import "RKPropertyInspector+CoreData.h"
#import "RKObjectUtilities.h"

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

/*
NOTE: At the moment this cache key assume that the structure of the values for each key in the `attributeValues` in constant
i.e. if you have `userID`, it will always be a single value, or `userIDs` will always be an array.
It will need to be reimplemented if changes in attribute values occur during the life of a single cache
*/
static NSString *RKPredicateCacheKeyForAttributes(NSArray *attributeNames)
{
return [[attributeNames sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] componentsJoinedByString:@":"];
}

// NOTE: We build a dynamic format string here because `NSCompoundPredicate` does not support use of substiution variables
static NSPredicate *RKPredicateWithSubsitutionVariablesForAttributes(NSArray *attributeNames)
static NSPredicate *RKPredicateWithSubsitutionVariablesForAttributeValues(NSDictionary *attributeValues)
{
NSArray *attributeNames = [attributeValues allKeys];
NSMutableArray *formatFragments = [NSMutableArray arrayWithCapacity:[attributeNames count]];
for (NSString *attributeName in attributeNames) {
NSString *formatFragment = [NSString stringWithFormat:@"%@ = $%@", attributeName, attributeName];
[attributeValues enumerateKeysAndObjectsUsingBlock:^(NSString *attributeName, id value, BOOL *stop) {
NSString *formatFragment = RKObjectIsCollection(value)
? [NSString stringWithFormat:@"%@ IN $%@", attributeName, attributeName]
: [NSString stringWithFormat:@"%@ = $%@", attributeName, attributeName];
[formatFragments addObject:formatFragment];
}
}];

return [NSPredicate predicateWithFormat:[formatFragments componentsJoinedByString:@" AND "]];
}
Expand All @@ -47,9 +56,9 @@ - (id)init
return self;
}

- (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity
attributeValues:(NSDictionary *)attributeValues
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
- (NSSet *)managedObjectsWithEntity:(NSEntityDescription *)entity
attributeValues:(NSDictionary *)attributeValues
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{

NSAssert(entity, @"Cannot find existing managed object without a target class");
Expand All @@ -59,7 +68,7 @@ - (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity
NSString *predicateCacheKey = RKPredicateCacheKeyForAttributes([attributeValues allKeys]);
NSPredicate *substitutionPredicate = [self.predicateCache objectForKey:predicateCacheKey];
if (! substitutionPredicate) {
substitutionPredicate = RKPredicateWithSubsitutionVariablesForAttributes([attributeValues allKeys]);
substitutionPredicate = RKPredicateWithSubsitutionVariablesForAttributeValues(attributeValues);
[self.predicateCache setObject:substitutionPredicate forKey:predicateCacheKey];
}

Expand All @@ -72,7 +81,7 @@ - (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity
}
RKLogDebug(@"Found objects '%@' using fetchRequest '%@'", objects, fetchRequest);

return objects;
return [NSSet setWithArray:objects];
}

@end
2 changes: 1 addition & 1 deletion Code/CoreData/RKInMemoryManagedObjectCache.m
Expand Up @@ -51,7 +51,7 @@ - (id)init
userInfo:nil];
}

- (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity
- (NSSet *)managedObjectsWithEntity:(NSEntityDescription *)entity
attributeValues:(NSDictionary *)attributeValues
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
Expand Down
6 changes: 3 additions & 3 deletions Code/CoreData/RKManagedObjectCaching.h
Expand Up @@ -38,9 +38,9 @@
@param attributeValues A dictionary specifying the attribute criteria for retrieving managed objects.
@param managedObjectContext The context to fetch the matching objects in.
*/
- (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity
attributeValues:(NSDictionary *)attributeValues
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
- (NSSet *)managedObjectsWithEntity:(NSEntityDescription *)entity
attributeValues:(NSDictionary *)attributeValues
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;

///---------------------------------------------------
/// @name Handling Managed Object Change Notifications
Expand Down
10 changes: 5 additions & 5 deletions Code/CoreData/RKManagedObjectMappingOperationDataSource.m
Expand Up @@ -161,12 +161,12 @@ - (id)mappingOperation:(RKMappingOperation *)mappingOperation targetObjectForRep
NSEntityDescription *entity = [entityMapping entity];
NSManagedObject *managedObject = nil;
if ([entityIdentifierAttributes count]) {
NSArray *objects = [self.managedObjectCache managedObjectsWithEntity:entity
attributeValues:entityIdentifierAttributes
inManagedObjectContext:self.managedObjectContext];
if (entityMapping.identificationPredicate) objects = [objects filteredArrayUsingPredicate:entityMapping.identificationPredicate];
NSSet *objects = [self.managedObjectCache managedObjectsWithEntity:entity
attributeValues:entityIdentifierAttributes
inManagedObjectContext:self.managedObjectContext];
if (entityMapping.identificationPredicate) objects = [objects filteredSetUsingPredicate:entityMapping.identificationPredicate];
if ([objects count] > 0) {
managedObject = objects[0];
managedObject = [objects anyObject];
if ([objects count] > 1) RKLogWarning(@"Managed object cache returned %ld objects for the identifier configured for the '%@' entity, expected 1.", (long) [objects count], [entity name]);
}
if (managedObject && [self.managedObjectCache respondsToSelector:@selector(didFetchObject:)]) {
Expand Down
3 changes: 3 additions & 0 deletions Code/CoreData/RKManagedObjectStore.m
Expand Up @@ -212,6 +212,9 @@ - (void)recreateManagedObjectContexts

- (BOOL)resetPersistentStores:(NSError **)error
{
[self.mainQueueManagedObjectContext reset];
[self.persistentStoreManagedObjectContext reset];

NSError *localError;
for (NSPersistentStore *persistentStore in self.persistentStoreCoordinator.persistentStores) {
NSURL *URL = [self.persistentStoreCoordinator URLForPersistentStore:persistentStore];
Expand Down
12 changes: 6 additions & 6 deletions Code/CoreData/RKRelationshipConnectionOperation.m
Expand Up @@ -140,15 +140,15 @@ - (id)findConnected
id connectionResult = nil;
if ([self.connection isForeignKeyConnection]) {
NSDictionary *attributeValues = RKConnectionAttributeValuesWithObject(self.connection, self.managedObject);
NSArray *managedObjects = [self.managedObjectCache managedObjectsWithEntity:[self.connection.relationship destinationEntity]
attributeValues:attributeValues
inManagedObjectContext:self.managedObjectContext];
if (self.connection.predicate) managedObjects = [managedObjects filteredArrayUsingPredicate:self.connection.predicate];
NSSet *managedObjects = [self.managedObjectCache managedObjectsWithEntity:[self.connection.relationship destinationEntity]
attributeValues:attributeValues
inManagedObjectContext:self.managedObjectContext];
if (self.connection.predicate) managedObjects = [managedObjects filteredSetUsingPredicate:self.connection.predicate];
if ([self.connection.relationship isToMany]) {
connectionResult = managedObjects;
} else {
if ([managedObjects count] > 1) RKLogWarning(@"Retrieved %ld objects satisfying connection criteria for one-to-one relationship connection: only the first result will be connected.", (long) [managedObjects count]);
if ([managedObjects count]) connectionResult = managedObjects[0];
if ([managedObjects count] > 1) RKLogWarning(@"Retrieved %ld objects satisfying connection criteria for one-to-one relationship connection: only object will be connected.", (long) [managedObjects count]);
if ([managedObjects count]) connectionResult = [managedObjects anyObject];
}
} else if ([self.connection isKeyPathConnection]) {
connectionResult = [self.managedObject valueForKeyPath:self.connection.keyPath];
Expand Down

0 comments on commit 1c60806

Please sign in to comment.