mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-24 04:46:01 +08:00
Major overhaul to the Core Data managed object identification and relationship connection support.
* Replaces primary key with `RKEntityIdentifier` * Add support for use of compound keys for object identification * Refactor `RKConnectionMapping` to `RKConnectionDescription` and add support for connecting with multiple attributes * Clarify naming of representation key methods to better match naming conventions * Add type transformation support for object identification * Greatly expand test coverage for object identification * Drop the `NSEntityDescription` category * Simplify the `RKManagedObjectCaching` protocol * Add compound key support to the Fetch Request and In Memory Cache implementations * Replace Kiwi with Specta for tests where contexts are helpful for organization * Rename `defaultValueForMissingAttribute` to `defaultValueForAttribute`
This commit is contained in:
@@ -10,7 +10,6 @@
|
||||
#import "NSManagedObjectContext+RKAdditions.h"
|
||||
#import "RKLog.h"
|
||||
#import "RKManagedObjectStore.h"
|
||||
#import "NSEntityDescription+RKAdditions.h"
|
||||
|
||||
@implementation NSManagedObject (RKAdditions)
|
||||
|
||||
|
||||
@@ -64,27 +64,27 @@
|
||||
*/
|
||||
- (NSUInteger)countForEntityForName:(NSString *)entityName predicate:(NSPredicate *)predicate error:(NSError **)error;
|
||||
|
||||
///----------------------------------------------
|
||||
/// @name Fetching Managed Objects by Primary Key
|
||||
///----------------------------------------------
|
||||
|
||||
/**
|
||||
Fetches a single managed object for the given entity with the given value for the primary key attribute in the receiver.
|
||||
|
||||
@param entity The entity of the managed object to be retrieved by primary key.
|
||||
@param primaryKeyValue The value for the entity's primary key attribute.
|
||||
@return The managed object with the primary key attribute equal to the given value or nil if none was found.
|
||||
*/
|
||||
- (id)fetchObjectForEntity:(NSEntityDescription *)entity withValueForPrimaryKeyAttribute:(id)primaryKeyValue;
|
||||
|
||||
/**
|
||||
Fetches a single managed object for the entity for the given name with the given value for the primary key attribute in the receiver.
|
||||
|
||||
@param entityName The name of the entity of the managed object to be retrieved by primary key.
|
||||
@param primaryKeyValue The value for the receiving entity's primary key attribute.
|
||||
@return The managed object with the primary key attribute equal to the given value or nil if none was found.
|
||||
*/
|
||||
- (id)fetchObjectForEntityForName:(NSString *)entityName withValueForPrimaryKeyAttribute:(id)primaryKeyValue;
|
||||
/////----------------------------------------------
|
||||
///// @name Fetching Managed Objects by Primary Key
|
||||
/////----------------------------------------------
|
||||
//
|
||||
///**
|
||||
// Fetches a single managed object for the given entity with the given value for the primary key attribute in the receiver.
|
||||
//
|
||||
// @param entity The entity of the managed object to be retrieved by primary key.
|
||||
// @param primaryKeyValue The value for the entity's primary key attribute.
|
||||
// @return The managed object with the primary key attribute equal to the given value or nil if none was found.
|
||||
// */
|
||||
//- (id)fetchObjectForEntity:(NSEntityDescription *)entity withValueForPrimaryKeyAttribute:(id)primaryKeyValue;
|
||||
//
|
||||
///**
|
||||
// Fetches a single managed object for the entity for the given name with the given value for the primary key attribute in the receiver.
|
||||
//
|
||||
// @param entityName The name of the entity of the managed object to be retrieved by primary key.
|
||||
// @param primaryKeyValue The value for the receiving entity's primary key attribute.
|
||||
// @return The managed object with the primary key attribute equal to the given value or nil if none was found.
|
||||
// */
|
||||
//- (id)fetchObjectForEntityForName:(NSString *)entityName withValueForPrimaryKeyAttribute:(id)primaryKeyValue;
|
||||
|
||||
///-------------------------------------------------
|
||||
/// @name Saving the Context to the Persistent Store
|
||||
|
||||
@@ -37,40 +37,40 @@
|
||||
return [self countForFetchRequest:fetchRequest error:error];
|
||||
}
|
||||
|
||||
- (id)fetchObjectForEntity:(NSEntityDescription *)entity withValueForPrimaryKeyAttribute:(id)primaryKeyValue
|
||||
{
|
||||
NSPredicate *predicate = [entity predicateForPrimaryKeyAttributeWithValue:primaryKeyValue];
|
||||
if (! predicate) {
|
||||
RKLogWarning(@"Attempt to fetchObjectForEntity for entity with nil primaryKeyAttribute. Set the primaryKeyAttributeName and try again! %@", self);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest new];
|
||||
fetchRequest.entity = entity;
|
||||
fetchRequest.predicate = predicate;
|
||||
fetchRequest.fetchLimit = 1;
|
||||
__block NSError *error;
|
||||
__block NSArray *objects;
|
||||
[self performBlockAndWait:^{
|
||||
objects = [self executeFetchRequest:fetchRequest error:&error];
|
||||
}];
|
||||
if (! objects) {
|
||||
RKLogCoreDataError(error);
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ([objects count] == 1) {
|
||||
return [objects objectAtIndex:0];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)fetchObjectForEntityForName:(NSString *)entityName withValueForPrimaryKeyAttribute:(id)primaryKeyValue
|
||||
{
|
||||
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:self];
|
||||
return [self fetchObjectForEntity:entity withValueForPrimaryKeyAttribute:primaryKeyValue];
|
||||
}
|
||||
//- (id)fetchObjectForEntity:(NSEntityDescription *)entity withValueForPrimaryKeyAttribute:(id)primaryKeyValue
|
||||
//{
|
||||
// NSPredicate *predicate = [entity predicateForPrimaryKeyAttributeWithValue:primaryKeyValue];
|
||||
// if (! predicate) {
|
||||
// RKLogWarning(@"Attempt to fetchObjectForEntity for entity with nil primaryKeyAttribute. Set the primaryKeyAttributeName and try again! %@", self);
|
||||
// return nil;
|
||||
// }
|
||||
//
|
||||
// NSFetchRequest *fetchRequest = [NSFetchRequest new];
|
||||
// fetchRequest.entity = entity;
|
||||
// fetchRequest.predicate = predicate;
|
||||
// fetchRequest.fetchLimit = 1;
|
||||
// __block NSError *error;
|
||||
// __block NSArray *objects;
|
||||
// [self performBlockAndWait:^{
|
||||
// objects = [self executeFetchRequest:fetchRequest error:&error];
|
||||
// }];
|
||||
// if (! objects) {
|
||||
// RKLogCoreDataError(error);
|
||||
// return nil;
|
||||
// }
|
||||
//
|
||||
// if ([objects count] == 1) {
|
||||
// return [objects objectAtIndex:0];
|
||||
// }
|
||||
//
|
||||
// return nil;
|
||||
//}
|
||||
//
|
||||
//- (id)fetchObjectForEntityForName:(NSString *)entityName withValueForPrimaryKeyAttribute:(id)primaryKeyValue
|
||||
//{
|
||||
// NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:self];
|
||||
// return [self fetchObjectForEntity:entity withValueForPrimaryKeyAttribute:primaryKeyValue];
|
||||
//}
|
||||
|
||||
- (BOOL)saveToPersistentStore:(NSError **)error
|
||||
{
|
||||
|
||||
26
Code/CoreData/RKConnectionDescription.h
Normal file
26
Code/CoreData/RKConnectionDescription.h
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// RKConnectionDescription.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 11/20/12.
|
||||
// Copyright (c) 2012 RestKit. All rights reserved.
|
||||
//
|
||||
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
/**
|
||||
*/
|
||||
@interface RKConnectionDescription : NSObject <NSCopying>
|
||||
|
||||
@property (nonatomic, strong, readonly) NSRelationshipDescription *relationship;
|
||||
|
||||
- (id)initWithRelationship:(NSRelationshipDescription *)relationship attributes:(NSDictionary *)sourceToDestinationEntityAttributes;
|
||||
- (id)initWithRelationship:(NSRelationshipDescription *)relationship keyPath:(NSString *)keyPath;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSDictionary *attributes; // nil unless foreign key
|
||||
- (BOOL)isForeignKeyConnection;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *keyPath; // nil unless keyPath description
|
||||
- (BOOL)isKeyPathConnection;
|
||||
|
||||
@end
|
||||
129
Code/CoreData/RKConnectionDescription.m
Normal file
129
Code/CoreData/RKConnectionDescription.m
Normal file
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// RKConnectionDescription.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 11/20/12.
|
||||
// Copyright (c) 2012 RestKit. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKConnectionDescription.h"
|
||||
|
||||
static NSSet *RKSetWithInvalidAttributesForEntity(NSArray *attributes, NSEntityDescription *entity)
|
||||
{
|
||||
NSMutableSet *attributesSet = [NSMutableSet setWithArray:attributes];
|
||||
NSSet *validAttributeNames = [NSSet setWithArray:[[entity attributesByName] allKeys]];
|
||||
[attributesSet minusSet:validAttributeNames];
|
||||
return attributesSet;
|
||||
}
|
||||
|
||||
// Provides support for connecting a relationship by
|
||||
@interface RKForeignKeyConnectionDescription : RKConnectionDescription
|
||||
@end
|
||||
|
||||
// Provides support for connecting a relationship by traversing the object graph
|
||||
@interface RKKeyPathConnectionDescription : RKConnectionDescription
|
||||
@end
|
||||
|
||||
@interface RKConnectionDescription ()
|
||||
@property (nonatomic, strong, readwrite) NSRelationshipDescription *relationship;
|
||||
@property (nonatomic, copy, readwrite) NSDictionary *attributes;
|
||||
@property (nonatomic, copy, readwrite) NSString *keyPath;
|
||||
@end
|
||||
|
||||
@implementation RKConnectionDescription
|
||||
|
||||
- (id)initWithRelationship:(NSRelationshipDescription *)relationship attributes:(NSDictionary *)attributes
|
||||
{
|
||||
NSParameterAssert(relationship);
|
||||
NSParameterAssert(attributes);
|
||||
NSAssert([attributes count], @"Cannot connect a relationship without at least one pair of attributes describing the connection");
|
||||
NSSet *invalidSourceAttributes = RKSetWithInvalidAttributesForEntity([attributes allKeys], [relationship entity]);
|
||||
NSAssert([invalidSourceAttributes count] == 0, @"Cannot connect relationship: invalid attributes given for source entity '%@': %@", [[relationship entity] name], [[invalidSourceAttributes allObjects] componentsJoinedByString:@", "]);
|
||||
NSSet *invalidDestinationAttributes = RKSetWithInvalidAttributesForEntity([attributes allValues], [relationship destinationEntity]);
|
||||
NSAssert([invalidDestinationAttributes count] == 0, @"Cannot connect relationship: invalid attributes given for destination entity '%@': %@", [[relationship destinationEntity] name], [[invalidDestinationAttributes allObjects] componentsJoinedByString:@", "]);
|
||||
|
||||
self = [[RKForeignKeyConnectionDescription alloc] init];
|
||||
if (self) {
|
||||
self.relationship = relationship;
|
||||
self.attributes = attributes;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithRelationship:(NSRelationshipDescription *)relationship keyPath:(NSString *)keyPath
|
||||
{
|
||||
NSParameterAssert(relationship);
|
||||
NSParameterAssert(keyPath);
|
||||
self = [[RKKeyPathConnectionDescription alloc] init];
|
||||
if (self) {
|
||||
self.relationship = relationship;
|
||||
self.keyPath = keyPath;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ([self class] == [RKConnectionDescription class]) {
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
reason:[NSString stringWithFormat:@"%@ Failed to call designated initializer. "
|
||||
"Invoke initWithRelationship:sourceKeyPath:destinationKeyPath:matcher: instead.",
|
||||
NSStringFromClass([self class])]
|
||||
userInfo:nil];
|
||||
}
|
||||
return [super init];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
if ([self isForeignKeyConnection]) {
|
||||
return [[[self class] allocWithZone:zone] initWithRelationship:self.relationship attributes:self.attributes];
|
||||
} else if ([self isKeyPathConnection]) {
|
||||
return [[[self class] allocWithZone:zone] initWithRelationship:self.relationship keyPath:self.keyPath];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)isForeignKeyConnection
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isKeyPathConnection
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RKForeignKeyConnectionDescription
|
||||
|
||||
- (BOOL)isForeignKeyConnection
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@:%p connecting Relationship '%@' from Entity '%@' to Destination Entity '%@' with attributes=%@>",
|
||||
NSStringFromClass([self class]), self, [self.relationship name], [[self.relationship entity] name],
|
||||
[[self.relationship destinationEntity] name], self.attributes];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RKKeyPathConnectionDescription
|
||||
|
||||
- (BOOL)isKeyPathConnection
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@:%p connecting Relationship '%@' of Entity '%@' with keyPath=%@>",
|
||||
NSStringFromClass([self class]), self, [self.relationship name], [[self.relationship entity] name], self.keyPath];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RKConnectionMapping;
|
||||
@class RKConnectionDescription;
|
||||
@protocol RKManagedObjectCaching;
|
||||
|
||||
/**
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
@see `RKConnectionMapping`
|
||||
*/
|
||||
@interface RKRelationshipConnectionOperation : NSOperation
|
||||
@interface RKConnectionOperation : NSOperation
|
||||
|
||||
///-------------------------------------------------------
|
||||
/// @name Initializing a Relationship Connection Operation
|
||||
@@ -45,7 +45,7 @@
|
||||
@return The receiver, initialized with the given managed object, connection mapping, and managed object cache.
|
||||
*/
|
||||
- (id)initWithManagedObject:(NSManagedObject *)managedObject
|
||||
connectionMapping:(RKConnectionMapping *)connectionMapping
|
||||
connection:(RKConnectionDescription *)connection
|
||||
managedObjectCache:(id<RKManagedObjectCaching>)managedObjectCache;
|
||||
|
||||
///--------------------------------------------
|
||||
@@ -60,7 +60,7 @@
|
||||
/**
|
||||
The connection mapping describing the relationship connection the receiver will attempt to connect.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) RKConnectionMapping *connectionMapping;
|
||||
@property (nonatomic, strong, readonly) RKConnectionDescription *connection;
|
||||
|
||||
/**
|
||||
The managed object cache the receiver will use to fetch a related object satisfying the connection mapping.
|
||||
@@ -19,7 +19,8 @@
|
||||
//
|
||||
|
||||
#import <CoreData/CoreData.h>
|
||||
#import "RKRelationshipConnectionOperation.h"
|
||||
#import "RKConnectionOperation.h"
|
||||
#import "RKConnectionDescription.h"
|
||||
#import "RKEntityMapping.h"
|
||||
#import "RKLog.h"
|
||||
#import "RKManagedObjectCaching.h"
|
||||
@@ -37,9 +38,21 @@ static id RKMutableSetValueForRelationship(NSRelationshipDescription *relationsh
|
||||
return [relationship isOrdered] ? [NSMutableOrderedSet orderedSet] : [NSMutableSet set];
|
||||
}
|
||||
|
||||
@interface RKRelationshipConnectionOperation ()
|
||||
static NSDictionary *RKConnectionAttributeValuesWithObject(RKConnectionDescription *connection, NSManagedObject *managedObject)
|
||||
{
|
||||
NSCAssert([connection isForeignKeyConnection], @"Only valid for a foreign key connection");
|
||||
NSMutableDictionary *destinationEntityAttributeValues = [NSMutableDictionary dictionaryWithCapacity:[connection.attributes count]];
|
||||
for (NSString *sourceAttribute in connection.attributes) {
|
||||
NSString *destinationAttribute = connection.attributes[sourceAttribute];
|
||||
id sourceValue = [managedObject valueForKey:sourceAttribute];
|
||||
[destinationEntityAttributeValues setValue:sourceValue forKey:destinationAttribute];
|
||||
}
|
||||
return destinationEntityAttributeValues;
|
||||
}
|
||||
|
||||
@interface RKConnectionOperation ()
|
||||
@property (nonatomic, strong, readwrite) NSManagedObject *managedObject;
|
||||
@property (nonatomic, strong, readwrite) RKConnectionMapping *connectionMapping;
|
||||
@property (nonatomic, strong, readwrite) RKConnectionDescription *connection;
|
||||
@property (nonatomic, strong, readwrite) id<RKManagedObjectCaching> managedObjectCache;
|
||||
@property (nonatomic, strong, readwrite) NSError *error;
|
||||
@property (nonatomic, strong, readwrite) id connectedValue;
|
||||
@@ -49,19 +62,20 @@ static id RKMutableSetValueForRelationship(NSRelationshipDescription *relationsh
|
||||
|
||||
@end
|
||||
|
||||
@implementation RKRelationshipConnectionOperation
|
||||
@implementation RKConnectionOperation
|
||||
|
||||
|
||||
- (id)initWithManagedObject:(NSManagedObject *)managedObject connectionMapping:(RKConnectionMapping *)connectionMapping managedObjectCache:(id<RKManagedObjectCaching>)managedObjectCache
|
||||
- (id)initWithManagedObject:(NSManagedObject *)managedObject
|
||||
connection:(RKConnectionDescription *)connection
|
||||
managedObjectCache:(id<RKManagedObjectCaching>)managedObjectCache;
|
||||
{
|
||||
NSParameterAssert(managedObject);
|
||||
NSAssert([managedObject isKindOfClass:[NSManagedObject class]], @"Relationship connection requires an instance of NSManagedObject");
|
||||
NSParameterAssert(connectionMapping);
|
||||
NSParameterAssert(connection);
|
||||
NSParameterAssert(managedObjectCache);
|
||||
self = [self init];
|
||||
if (self) {
|
||||
self.managedObject = managedObject;
|
||||
self.connectionMapping = connectionMapping;
|
||||
self.connection = connection;
|
||||
self.managedObjectCache = managedObjectCache;
|
||||
}
|
||||
|
||||
@@ -73,23 +87,14 @@ static id RKMutableSetValueForRelationship(NSRelationshipDescription *relationsh
|
||||
return self.managedObject.managedObjectContext;
|
||||
}
|
||||
|
||||
- (NSManagedObject *)findOneConnectedWithSourceValue:(id)sourceValue
|
||||
{
|
||||
NSAssert(self.managedObjectContext, @"Cannot lookup objects with a nil managedObjectContext");
|
||||
return [self.managedObjectCache findInstanceOfEntity:self.connectionMapping.relationship.destinationEntity
|
||||
withPrimaryKeyAttribute:self.connectionMapping.destinationKeyPath
|
||||
value:sourceValue
|
||||
inManagedObjectContext:self.managedObjectContext];
|
||||
}
|
||||
|
||||
- (id)relationshipValueWithConnectionResult:(id)result
|
||||
{
|
||||
// TODO: Replace with use of object mapping engine for type conversion
|
||||
|
||||
// NOTE: This is a nasty hack to work around the fact that NSOrderedSet does not support key-value
|
||||
// collection operators. We try to detect and unpack a doubly wrapped collection
|
||||
if ([self.connectionMapping.relationship isToMany] && RKObjectIsCollectionOfCollections(result)) {
|
||||
id mutableSet = RKMutableSetValueForRelationship(self.connectionMapping.relationship);
|
||||
if ([self.connection.relationship isToMany] && RKObjectIsCollectionOfCollections(result)) {
|
||||
id mutableSet = RKMutableSetValueForRelationship(self.connection.relationship);
|
||||
for (id<NSFastEnumeration> enumerable in result) {
|
||||
for (id object in enumerable) {
|
||||
[mutableSet addObject:object];
|
||||
@@ -99,27 +104,27 @@ static id RKMutableSetValueForRelationship(NSRelationshipDescription *relationsh
|
||||
return mutableSet;
|
||||
}
|
||||
|
||||
if ([self.connectionMapping.relationship isToMany]) {
|
||||
if ([self.connection.relationship isToMany]) {
|
||||
if ([result isKindOfClass:[NSArray class]]) {
|
||||
if ([self.connectionMapping.relationship isOrdered]) {
|
||||
if ([self.connection.relationship isOrdered]) {
|
||||
return [NSOrderedSet orderedSetWithArray:result];
|
||||
} else {
|
||||
return [NSSet setWithArray:result];
|
||||
}
|
||||
} else if ([result isKindOfClass:[NSSet class]]) {
|
||||
if ([self.connectionMapping.relationship isOrdered]) {
|
||||
if ([self.connection.relationship isOrdered]) {
|
||||
return [NSOrderedSet orderedSetWithSet:result];
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
} else if ([result isKindOfClass:[NSOrderedSet class]]) {
|
||||
if ([self.connectionMapping.relationship isOrdered]) {
|
||||
if ([self.connection.relationship isOrdered]) {
|
||||
return result;
|
||||
} else {
|
||||
return [(NSOrderedSet *)result set];
|
||||
}
|
||||
} else {
|
||||
if ([self.connectionMapping.relationship isOrdered]) {
|
||||
if ([self.connection.relationship isOrdered]) {
|
||||
return [NSOrderedSet orderedSetWithObject:result];
|
||||
} else {
|
||||
return [NSSet setWithObject:result];
|
||||
@@ -143,61 +148,56 @@ static id RKMutableSetValueForRelationship(NSRelationshipDescription *relationsh
|
||||
|
||||
for (id value in values) {
|
||||
NSAssert(self.managedObjectContext, @"Cannot lookup objects with a nil managedObjectContext");
|
||||
NSArray *objects = [self.managedObjectCache findInstancesOfEntity:self.connectionMapping.relationship.destinationEntity
|
||||
withPrimaryKeyAttribute:self.connectionMapping.destinationKeyPath
|
||||
value:value
|
||||
inManagedObjectContext:self.managedObjectContext];
|
||||
[result addObjectsFromArray:objects];
|
||||
// NSArray *objects = [self.managedObjectCache findInstancesOfEntity:self.connectionMapping.relationship.destinationEntity
|
||||
// withPrimaryKeyAttribute:self.connectionMapping.destinationKeyPath
|
||||
// value:value
|
||||
// inManagedObjectContext:self.managedObjectContext];
|
||||
// [result addObjectsFromArray:objects];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)isToMany
|
||||
{
|
||||
return self.connectionMapping.relationship.isToMany;
|
||||
}
|
||||
|
||||
- (BOOL)checkMatcher
|
||||
{
|
||||
if (!self.connectionMapping.matcher) {
|
||||
return YES;
|
||||
} else {
|
||||
return [self.connectionMapping.matcher matches:self.managedObject];
|
||||
}
|
||||
}
|
||||
//- (NSManagedObject *)findOneConnectedWithSourceValue:(id)sourceValue
|
||||
//{
|
||||
// NSAssert(self.managedObjectContext, @"Cannot lookup objects with a nil managedObjectContext");
|
||||
// NSArray *
|
||||
// return [self.managedObjectCache findInstanceOfEntity:self.connectionMapping.relationship.destinationEntity
|
||||
// withPrimaryKeyAttribute:self.connectionMapping.destinationKeyPath
|
||||
// value:sourceValue
|
||||
// inManagedObjectContext:self.managedObjectContext];
|
||||
//}
|
||||
|
||||
- (id)findConnected
|
||||
{
|
||||
if ([self checkMatcher]) {
|
||||
id connectionResult = nil;
|
||||
if ([self.connectionMapping isForeignKeyConnection]) {
|
||||
BOOL isToMany = [self isToMany];
|
||||
id sourceValue = [self.managedObject valueForKey:self.connectionMapping.sourceKeyPath];
|
||||
if (isToMany) {
|
||||
connectionResult = [self findAllConnectedWithSourceValue:sourceValue];
|
||||
} else {
|
||||
connectionResult = [self findOneConnectedWithSourceValue:sourceValue];
|
||||
}
|
||||
} else if ([self.connectionMapping isKeyPathConnection]) {
|
||||
connectionResult = [self.managedObject valueForKeyPath:self.connectionMapping.sourceKeyPath];
|
||||
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.relationship isToMany]) {
|
||||
connectionResult = managedObjects;
|
||||
} else {
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
reason:[NSString stringWithFormat:@"%@ Attempted to establish a relationship using a mapping"
|
||||
"specifies neither a foreign key or a key path connection: %@",
|
||||
NSStringFromClass([self class]), self.connectionMapping]
|
||||
userInfo:nil];
|
||||
if ([managedObjects count] > 1) RKLogWarning(@"Retrieved %d objects satisfying connection criteria for one-to-one relationship connection: only the first result will be connected.", [managedObjects count]);
|
||||
connectionResult = managedObjects[0];
|
||||
}
|
||||
|
||||
return [self relationshipValueWithConnectionResult:connectionResult];
|
||||
} else if ([self.connection isKeyPathConnection]) {
|
||||
connectionResult = [self.managedObject valueForKeyPath:self.connection.keyPath];
|
||||
} else {
|
||||
return nil;
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
reason:[NSString stringWithFormat:@"%@ Attempted to establish a relationship using a mapping that"
|
||||
" specifies neither a foreign key or a key path connection: %@",
|
||||
NSStringFromClass([self class]), self.connection]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
return [self relationshipValueWithConnectionResult:connectionResult];
|
||||
}
|
||||
|
||||
- (void)connectRelationship
|
||||
{
|
||||
NSString *relationshipName = self.connectionMapping.relationship.name;
|
||||
RKLogTrace(@"Connecting relationship '%@' with mapping: %@", relationshipName, self.connectionMapping);
|
||||
NSString *relationshipName = self.connection.relationship.name;
|
||||
RKLogTrace(@"Connecting relationship '%@' with mapping: %@", relationshipName, self.connection);
|
||||
[self.managedObjectContext performBlockAndWait:^{
|
||||
self.connectedValue = [self findConnected];
|
||||
[self.managedObject setValue:self.connectedValue forKeyPath:relationshipName];
|
||||
@@ -214,7 +214,7 @@ static id RKMutableSetValueForRelationship(NSRelationshipDescription *relationsh
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@:%p %@ in %@ using %@>",
|
||||
[self class], self, self.connectionMapping, self.managedObjectContext, self.managedObjectCache];
|
||||
[self class], self, self.connection, self.managedObjectContext, self.managedObjectCache];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -41,12 +41,12 @@
|
||||
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 attributeNames An array of attribute names used as the cache keys.
|
||||
@param context 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;
|
||||
- (id)initWithEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributeNames managedObjectContext:(NSManagedObjectContext *)context;
|
||||
|
||||
///-----------------------------
|
||||
/// @name Getting Cache Identity
|
||||
@@ -58,9 +58,9 @@
|
||||
@property (nonatomic, readonly) NSEntityDescription *entity;
|
||||
|
||||
/**
|
||||
An attribute that is part of the cached entity that acts as the cache key.
|
||||
An array of attribute names specifying attributes of the cached entity that act as the cache key.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *attribute;
|
||||
@property (nonatomic, readonly) NSArray *attributes;
|
||||
|
||||
/**
|
||||
The managed object context the receiver fetches cached objects from.
|
||||
@@ -105,14 +105,12 @@
|
||||
- (NSUInteger)count;
|
||||
|
||||
/**
|
||||
Returns the total number of cached objects with a given value for the attribute acting as the cache key.
|
||||
Returns the total number of cached objects whose attributes match the values in the given dictionary of attribute values.
|
||||
|
||||
@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.
|
||||
@param attributeValues 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;
|
||||
- (NSUInteger)countWithAttributeValues:(NSDictionary *)attributeValues;
|
||||
|
||||
/**
|
||||
Returns the number of unique attribute values contained within the receiver.
|
||||
@@ -137,17 +135,16 @@
|
||||
@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;
|
||||
- (BOOL)containsObjectWithAttributeValues:(NSDictionary *)attributeValues;
|
||||
|
||||
/**
|
||||
Returns the first object with a matching value for the cache key attribute
|
||||
in a given managed object context.
|
||||
Returns the first object with a matching value for the cache key attributes in a given managed object context.
|
||||
|
||||
@param attributeValue A value for the cache key attribute.
|
||||
@param attributeValues A value for the cache key attribute.
|
||||
@param context The managed object context to retrieve the object from.
|
||||
@return An object with the value of attribute matching attributeValue or nil.
|
||||
*/
|
||||
- (NSManagedObject *)objectWithAttributeValue:(id)attributeValue inContext:(NSManagedObjectContext *)context;
|
||||
- (NSManagedObject *)objectWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context;
|
||||
|
||||
/**
|
||||
Returns the collection of objects with a matching value for the cache key attribute in a given managed object context.
|
||||
@@ -156,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 *)objectsWithAttributeValue:(id)attributeValue inContext:(NSManagedObjectContext *)context;
|
||||
- (NSArray *)objectsWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context;
|
||||
|
||||
///------------------------------
|
||||
/// @name Managing Cached Objects
|
||||
|
||||
@@ -32,23 +32,44 @@
|
||||
#undef RKLogComponent
|
||||
#define RKLogComponent RKlcl_cRestKitCoreDataCache
|
||||
|
||||
static id RKCacheKeyValueForEntityAttributeWithValue(NSEntityDescription *entity, NSString *attribute, id value)
|
||||
{
|
||||
if ([value isKindOfClass:[NSString class]] || [value isEqual:[NSNull null]]) {
|
||||
return value;
|
||||
}
|
||||
|
||||
Class attributeType = [[RKPropertyInspector sharedInspector] classForPropertyNamed:attribute ofEntity:entity];
|
||||
return [attributeType instancesRespondToSelector:@selector(stringValue)] ? [value stringValue] : value;
|
||||
}
|
||||
|
||||
static NSString *RKCacheKeyForEntityWithAttributeValues(NSEntityDescription *entity, NSDictionary *attributeValues)
|
||||
{
|
||||
NSArray *sortedAttributes = [[attributeValues allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
|
||||
NSMutableArray *sortedValues = [NSMutableArray arrayWithCapacity:[sortedAttributes count]];
|
||||
[sortedAttributes enumerateObjectsUsingBlock:^(NSString *attributeName, NSUInteger idx, BOOL *stop) {
|
||||
id cacheKeyValue = RKCacheKeyValueForEntityAttributeWithValue(entity, attributeName, attributeValues[attributeName]);
|
||||
[sortedValues addObject:cacheKeyValue];
|
||||
}];
|
||||
|
||||
return [sortedValues componentsJoinedByString:@":"];
|
||||
}
|
||||
|
||||
@interface RKEntityByAttributeCache ()
|
||||
@property (nonatomic, strong) NSMutableDictionary *attributeValuesToObjectIDs;
|
||||
@property (nonatomic, strong) NSMutableDictionary *cacheKeysToObjectIDs;
|
||||
@end
|
||||
|
||||
@implementation RKEntityByAttributeCache
|
||||
|
||||
|
||||
- (id)initWithEntity:(NSEntityDescription *)entity attribute:(NSString *)attributeName managedObjectContext:(NSManagedObjectContext *)context
|
||||
- (id)initWithEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributeNames managedObjectContext:(NSManagedObjectContext *)context
|
||||
{
|
||||
NSParameterAssert(entity);
|
||||
NSParameterAssert(attributeName);
|
||||
NSParameterAssert(attributeNames);
|
||||
NSParameterAssert(context);
|
||||
|
||||
self = [self init];
|
||||
if (self) {
|
||||
_entity = entity;
|
||||
_attribute = attributeName;
|
||||
_attributes = attributeNames;
|
||||
_managedObjectContext = context;
|
||||
_monitorsContextForChanges = YES;
|
||||
|
||||
@@ -75,35 +96,25 @@
|
||||
|
||||
- (NSUInteger)count
|
||||
{
|
||||
return [[[self.attributeValuesToObjectIDs allValues] valueForKeyPath:@"@sum.@count"] integerValue];
|
||||
return [[[self.cacheKeysToObjectIDs allValues] valueForKeyPath:@"@sum.@count"] integerValue];
|
||||
}
|
||||
|
||||
- (NSUInteger)countOfAttributeValues
|
||||
{
|
||||
return [self.attributeValuesToObjectIDs count];
|
||||
return [self.cacheKeysToObjectIDs count];
|
||||
}
|
||||
|
||||
- (NSUInteger)countWithAttributeValue:(id)attributeValue
|
||||
- (NSUInteger)countWithAttributeValues:(NSDictionary *)attributeValues
|
||||
{
|
||||
return [[self objectsWithAttributeValue:attributeValue inContext:self.managedObjectContext] count];
|
||||
}
|
||||
|
||||
- (BOOL)shouldCoerceAttributeToString:(NSString *)attributeValue
|
||||
{
|
||||
if ([attributeValue isKindOfClass:[NSString class]] || [attributeValue isEqual:[NSNull null]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
Class attributeType = [[RKPropertyInspector sharedInspector] classForPropertyNamed:self.attribute ofEntity:self.entity];
|
||||
return [attributeType instancesRespondToSelector:@selector(stringValue)];
|
||||
return [[self objectsWithAttributeValues:attributeValues inContext:self.managedObjectContext] count];
|
||||
}
|
||||
|
||||
- (void)load
|
||||
{
|
||||
RKLogDebug(@"Loading entity cache for Entity '%@' by attribute '%@' in managed object context %@ (concurrencyType = %ld)",
|
||||
self.entity.name, self.attribute, self.managedObjectContext, (unsigned long)self.managedObjectContext.concurrencyType);
|
||||
@synchronized(self.attributeValuesToObjectIDs) {
|
||||
self.attributeValuesToObjectIDs = [NSMutableDictionary dictionary];
|
||||
RKLogDebug(@"Loading entity cache for Entity '%@' by attributes '%@' in managed object context %@ (concurrencyType = %ld)",
|
||||
self.entity.name, self.attributes, self.managedObjectContext, (unsigned long)self.managedObjectContext.concurrencyType);
|
||||
@synchronized(self.cacheKeysToObjectIDs) {
|
||||
self.cacheKeysToObjectIDs = [NSMutableDictionary dictionary];
|
||||
|
||||
NSExpressionDescription* objectIDExpression = [NSExpressionDescription new];
|
||||
objectIDExpression.name = @"objectID";
|
||||
@@ -114,7 +125,7 @@
|
||||
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
|
||||
fetchRequest.entity = self.entity;
|
||||
fetchRequest.resultType = NSDictionaryResultType;
|
||||
fetchRequest.propertiesToFetch = [NSArray arrayWithObjects:objectIDExpression, self.attribute, nil];
|
||||
fetchRequest.propertiesToFetch = [self.attributes arrayByAddingObject:objectIDExpression];
|
||||
|
||||
[self.managedObjectContext performBlockAndWait:^{
|
||||
NSError *error = nil;
|
||||
@@ -127,9 +138,9 @@
|
||||
}
|
||||
|
||||
for (NSDictionary *dictionary in dictionaries) {
|
||||
id attributeValue = [dictionary objectForKey:self.attribute];
|
||||
NSManagedObjectID *objectID = [dictionary objectForKey:@"objectID"];
|
||||
[self setObjectID:objectID forAttributeValue:attributeValue];
|
||||
NSDictionary *attributeValues = [dictionary dictionaryWithValuesForKeys:self.attributes];
|
||||
[self setObjectID:objectID forAttributeValues:attributeValues];
|
||||
}
|
||||
}];
|
||||
}
|
||||
@@ -137,9 +148,9 @@
|
||||
|
||||
- (void)flush
|
||||
{
|
||||
@synchronized(self.attributeValuesToObjectIDs) {
|
||||
RKLogDebug(@"Flushing entity cache for Entity '%@' by attribute '%@'", self.entity.name, self.attribute);
|
||||
self.attributeValuesToObjectIDs = nil;
|
||||
@synchronized(self.cacheKeysToObjectIDs) {
|
||||
RKLogDebug(@"Flushing entity cache for Entity '%@' by attributes '%@'", self.entity.name, self.attributes);
|
||||
self.cacheKeysToObjectIDs = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +162,7 @@
|
||||
|
||||
- (BOOL)isLoaded
|
||||
{
|
||||
return (self.attributeValuesToObjectIDs != nil);
|
||||
return (self.cacheKeysToObjectIDs != nil);
|
||||
}
|
||||
|
||||
- (NSManagedObject *)objectForObjectID:(NSManagedObjectID *)objectID inContext:(NSManagedObjectContext *)context
|
||||
@@ -180,18 +191,19 @@
|
||||
return object;
|
||||
}
|
||||
|
||||
- (NSManagedObject *)objectWithAttributeValue:(id)attributeValue inContext:(NSManagedObjectContext *)context
|
||||
- (NSManagedObject *)objectWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context
|
||||
{
|
||||
NSArray *objects = [self objectsWithAttributeValue:attributeValue inContext:context];
|
||||
NSArray *objects = [self objectsWithAttributeValues:attributeValues inContext:context];
|
||||
return ([objects count] > 0) ? [objects objectAtIndex:0] : nil;
|
||||
}
|
||||
|
||||
- (NSArray *)objectsWithAttributeValue:(id)attributeValue inContext:(NSManagedObjectContext *)context
|
||||
- (NSArray *)objectsWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context
|
||||
{
|
||||
attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
|
||||
// TODO: Assert that the attribute values contains all of the cache attributes!!!
|
||||
NSString *cacheKey = RKCacheKeyForEntityWithAttributeValues(self.entity, attributeValues);
|
||||
NSArray *objectIDs = nil;
|
||||
@synchronized(self.attributeValuesToObjectIDs) {
|
||||
objectIDs = [[NSArray alloc] initWithArray:[self.attributeValuesToObjectIDs objectForKey:attributeValue] copyItems:YES];
|
||||
@synchronized(self.cacheKeysToObjectIDs) {
|
||||
objectIDs = [[NSArray alloc] initWithArray:[self.cacheKeysToObjectIDs objectForKey:cacheKey] copyItems:YES];
|
||||
}
|
||||
if ([objectIDs count]) {
|
||||
/**
|
||||
@@ -205,8 +217,8 @@
|
||||
if (object) {
|
||||
[objects addObject:object];
|
||||
} else {
|
||||
RKLogDebug(@"Evicting objectID association for attribute '%@'=>'%@' of Entity '%@': %@", self.attribute, attributeValue, self.entity.name, objectID);
|
||||
[self removeObjectID:objectID forAttributeValue:attributeValue];
|
||||
RKLogDebug(@"Evicting objectID association for attributes %@ of Entity '%@': %@", attributeValues, self.entity.name, objectID);
|
||||
[self removeObjectID:objectID forAttributeValues:attributeValues];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,12 +228,12 @@
|
||||
return [NSArray array];
|
||||
}
|
||||
|
||||
- (void)setObjectID:(NSManagedObjectID *)objectID forAttributeValue:(id)attributeValue
|
||||
- (void)setObjectID:(NSManagedObjectID *)objectID forAttributeValues:(NSDictionary *)attributeValues
|
||||
{
|
||||
@synchronized(self.attributeValuesToObjectIDs) {
|
||||
attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
|
||||
if (attributeValue) {
|
||||
NSMutableArray *objectIDs = [self.attributeValuesToObjectIDs objectForKey:attributeValue];
|
||||
@synchronized(self.cacheKeysToObjectIDs) {
|
||||
if (attributeValues && [attributeValues count]) {
|
||||
NSString *cacheKey = RKCacheKeyForEntityWithAttributeValues(self.entity, attributeValues);
|
||||
NSMutableArray *objectIDs = [self.cacheKeysToObjectIDs objectForKey:cacheKey];
|
||||
if (objectIDs) {
|
||||
if (! [objectIDs containsObject:objectID]) {
|
||||
[objectIDs addObject:objectID];
|
||||
@@ -230,27 +242,25 @@
|
||||
objectIDs = [NSMutableArray arrayWithObject:objectID];
|
||||
}
|
||||
|
||||
|
||||
if (nil == self.attributeValuesToObjectIDs) self.attributeValuesToObjectIDs = [NSMutableDictionary dictionary];
|
||||
[self.attributeValuesToObjectIDs setValue:objectIDs forKey:attributeValue];
|
||||
if (nil == self.cacheKeysToObjectIDs) self.cacheKeysToObjectIDs = [NSMutableDictionary dictionary];
|
||||
[self.cacheKeysToObjectIDs setValue:objectIDs forKey:cacheKey];
|
||||
} else {
|
||||
RKLogWarning(@"Unable to add object for object ID %@: nil value for attribute '%@'", objectID, self.attribute);
|
||||
RKLogWarning(@"Unable to add object for object ID %@: empty values dictionary for attributes '%@'", objectID, self.attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeObjectID:(NSManagedObjectID *)objectID forAttributeValue:(id)attributeValue
|
||||
- (void)removeObjectID:(NSManagedObjectID *)objectID forAttributeValues:(NSDictionary *)attributeValues
|
||||
{
|
||||
@synchronized(self.attributeValuesToObjectIDs) {
|
||||
// Coerce to a string if possible
|
||||
attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
|
||||
if (attributeValue) {
|
||||
NSMutableArray *objectIDs = [self.attributeValuesToObjectIDs objectForKey:attributeValue];
|
||||
@synchronized(self.cacheKeysToObjectIDs) {
|
||||
if (attributeValues && [attributeValues count]) {
|
||||
NSString *cacheKey = RKCacheKeyForEntityWithAttributeValues(self.entity, attributeValues);
|
||||
NSMutableArray *objectIDs = [self.cacheKeysToObjectIDs objectForKey:cacheKey];
|
||||
if (objectIDs && [objectIDs containsObject:objectID]) {
|
||||
[objectIDs removeObject:objectID];
|
||||
}
|
||||
} else {
|
||||
RKLogWarning(@"Unable to remove object for object ID %@: nil value for attribute '%@'", objectID, self.attribute);
|
||||
RKLogWarning(@"Unable to remove object for object ID %@: empty values dictionary for attributes '%@'", objectID, self.attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,42 +268,39 @@
|
||||
- (void)addObject:(NSManagedObject *)object
|
||||
{
|
||||
__block NSEntityDescription *entity;
|
||||
__block id attributeValue;
|
||||
__block NSDictionary *attributeValues;
|
||||
__block NSManagedObjectID *objectID;
|
||||
[self.managedObjectContext performBlockAndWait:^{
|
||||
entity = object.entity;
|
||||
objectID = [object objectID];
|
||||
attributeValue = [object valueForKey:self.attribute];
|
||||
attributeValues = [object dictionaryWithValuesForKeys:self.attributes];
|
||||
}];
|
||||
NSAssert([entity isKindOfEntity:self.entity], @"Cannot add object with entity '%@' to cache for entity of '%@'", [entity name], [self.entity name]);
|
||||
// Coerce to a string if possible
|
||||
[self setObjectID:objectID forAttributeValue:attributeValue];
|
||||
[self setObjectID:objectID forAttributeValues:attributeValues];
|
||||
}
|
||||
|
||||
- (void)removeObject:(NSManagedObject *)object
|
||||
{
|
||||
__block NSEntityDescription *entity;
|
||||
__block id attributeValue;
|
||||
__block NSDictionary *attributeValues;
|
||||
__block NSManagedObjectID *objectID;
|
||||
[object.managedObjectContext performBlockAndWait:^{
|
||||
entity = object.entity;
|
||||
objectID = [object objectID];
|
||||
attributeValue = [object valueForKey:self.attribute];
|
||||
attributeValues = [object dictionaryWithValuesForKeys:self.attributes];
|
||||
}];
|
||||
NSAssert([entity isKindOfEntity:self.entity], @"Cannot remove object with entity '%@' from cache for entity of '%@'", [entity name], [self.entity name]);
|
||||
[self removeObjectID:objectID forAttributeValue:attributeValue];
|
||||
[self removeObjectID:objectID forAttributeValues:attributeValues];
|
||||
}
|
||||
|
||||
- (BOOL)containsObjectWithAttributeValue:(id)attributeValue
|
||||
- (BOOL)containsObjectWithAttributeValues:(NSDictionary *)attributeValues
|
||||
{
|
||||
// Coerce to a string if possible
|
||||
attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
|
||||
return [[self objectsWithAttributeValue:attributeValue inContext:self.managedObjectContext] count] > 0;
|
||||
return [[self objectsWithAttributeValues:attributeValues inContext:self.managedObjectContext] count] > 0;
|
||||
}
|
||||
|
||||
- (BOOL)containsObject:(NSManagedObject *)object
|
||||
{
|
||||
NSArray *allObjectIDs = [[self.attributeValuesToObjectIDs allValues] valueForKeyPath:@"@distinctUnionOfArrays.self"];
|
||||
NSArray *allObjectIDs = [[self.cacheKeysToObjectIDs allValues] valueForKeyPath:@"@distinctUnionOfArrays.self"];
|
||||
return [allObjectIDs containsObject:object.objectID];
|
||||
}
|
||||
|
||||
|
||||
@@ -50,50 +50,48 @@
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
|
||||
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Caching Objects by Attribute
|
||||
///-----------------------------------------------------------------------------
|
||||
///------------------------------------
|
||||
/// @name Caching Objects by Attributes
|
||||
///------------------------------------
|
||||
|
||||
/**
|
||||
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.
|
||||
@param attributeNames The attributes to cache the instances by.
|
||||
*/
|
||||
- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttribute:(NSString *)attributeName;
|
||||
- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttributes:(NSArray *)attributeNames;
|
||||
|
||||
/**
|
||||
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.
|
||||
@param attributeNames The attributes 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;
|
||||
- (BOOL)isEntity:(NSEntityDescription *)entity cachedByAttributes:(NSArray *)attributeNames;
|
||||
|
||||
/**
|
||||
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.
|
||||
@param attributeValues The attribute values return a match for.
|
||||
@param context The managed object from which to retrieve the cached results.
|
||||
@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 inContext:(NSManagedObjectContext *)context;
|
||||
- (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context;
|
||||
|
||||
/**
|
||||
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.
|
||||
@param attributeValues The attribute values return a match for.
|
||||
@param context The managed object from which to retrieve the cached results.
|
||||
@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 inContext:(NSManagedObjectContext *)context;
|
||||
- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context;
|
||||
|
||||
///-----------------------------------------------------------------------------
|
||||
// @name Accessing Underlying Caches
|
||||
@@ -106,7 +104,7 @@
|
||||
@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;
|
||||
- (RKEntityByAttributeCache *)attributeCacheForEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributeNames;
|
||||
|
||||
/**
|
||||
Retrieves all entity attributes caches for a given entity.
|
||||
|
||||
@@ -46,58 +46,60 @@
|
||||
}
|
||||
|
||||
|
||||
- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttribute:(NSString *)attributeName
|
||||
- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttributes:(NSArray *)attributeNames
|
||||
{
|
||||
NSAssert(entity, @"Cannot cache objects for a nil entity");
|
||||
NSAssert(attributeName, @"Cannot cache objects without an attribute");
|
||||
RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attribute:attributeName];
|
||||
NSParameterAssert(entity);
|
||||
NSParameterAssert(attributeNames);
|
||||
RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attributes:attributeNames];
|
||||
if (attributeCache && !attributeCache.isLoaded) {
|
||||
[attributeCache load];
|
||||
} else {
|
||||
attributeCache = [[RKEntityByAttributeCache alloc] initWithEntity:entity attribute:attributeName managedObjectContext:self.managedObjectContext];
|
||||
attributeCache = [[RKEntityByAttributeCache alloc] initWithEntity:entity attributes:attributeNames managedObjectContext:self.managedObjectContext];
|
||||
[attributeCache load];
|
||||
[self.attributeCaches addObject:attributeCache];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isEntity:(NSEntityDescription *)entity cachedByAttribute:(NSString *)attributeName
|
||||
- (BOOL)isEntity:(NSEntityDescription *)entity cachedByAttributes:(NSArray *)attributeNames
|
||||
{
|
||||
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];
|
||||
NSParameterAssert(entity);
|
||||
NSParameterAssert(attributeNames);
|
||||
RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attributes:attributeNames];
|
||||
return (attributeCache && attributeCache.isLoaded);
|
||||
}
|
||||
|
||||
- (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue inContext:(NSManagedObjectContext *)context
|
||||
- (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context
|
||||
{
|
||||
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];
|
||||
NSParameterAssert(entity);
|
||||
NSParameterAssert(attributeValues);
|
||||
NSParameterAssert(context);
|
||||
RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attributes:[attributeValues allKeys]];
|
||||
if (attributeCache) {
|
||||
return [attributeCache objectWithAttributeValue:attributeValue inContext:context];
|
||||
return [attributeCache objectWithAttributeValues:attributeValues inContext:context];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue inContext:(NSManagedObjectContext *)context
|
||||
- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context
|
||||
{
|
||||
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];
|
||||
NSParameterAssert(entity);
|
||||
NSParameterAssert(attributeValues);
|
||||
NSParameterAssert(context);
|
||||
RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attributes:[attributeValues allKeys]];
|
||||
if (attributeCache) {
|
||||
return [attributeCache objectsWithAttributeValue:attributeValue inContext:context];
|
||||
return [attributeCache objectsWithAttributeValues:attributeValues inContext:context];
|
||||
}
|
||||
|
||||
return [NSSet set];
|
||||
}
|
||||
|
||||
- (RKEntityByAttributeCache *)attributeCacheForEntity:(NSEntityDescription *)entity attribute:(NSString *)attributeName
|
||||
- (RKEntityByAttributeCache *)attributeCacheForEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributeNames
|
||||
{
|
||||
NSAssert(entity, @"Cannot retrieve attribute cache for a nil entity");
|
||||
NSAssert(attributeName, @"Cannot retrieve attribute cache for a nil attribute");
|
||||
NSParameterAssert(entity);
|
||||
NSParameterAssert(attributeNames);
|
||||
for (RKEntityByAttributeCache *cache in self.attributeCaches) {
|
||||
if ([cache.entity isEqual:entity] && [cache.attribute isEqualToString:attributeName]) {
|
||||
if ([cache.entity isEqual:entity] && [cache.attributes isEqualToArray:attributeNames]) {
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
39
Code/CoreData/RKEntityIdentifier.h
Normal file
39
Code/CoreData/RKEntityIdentifier.h
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// RKEntityIdentifier.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 11/20/12.
|
||||
// Copyright (c) 2012 RestKit. All rights reserved.
|
||||
//
|
||||
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
extern NSString * const RKEntityIdentifierUserInfoKey;
|
||||
|
||||
@class RKManagedObjectStore;
|
||||
|
||||
// RKEntityIdentifier | RKManagedObjectIdentifier | RKEntityIdentity | RKResourceIdentity | RKEntityKey | RKIdentifier
|
||||
@interface RKEntityIdentifier : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSEntityDescription *entity;
|
||||
@property (nonatomic, copy, readonly) NSArray *attributes;
|
||||
|
||||
// Convenience method
|
||||
// identifierWithEntityName:???
|
||||
// entityIdentifierWithName:
|
||||
+ (id)identifierWithEntityName:(NSString *)entityName attributes:(NSArray *)attributes inManagedObjectStore:(RKManagedObjectStore *)managedObjectStore;
|
||||
|
||||
// Designated initializer
|
||||
- (id)initWithEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributes;
|
||||
|
||||
// Optional predicate for filtering matches
|
||||
@property (nonatomic, copy) NSPredicate *predicate;
|
||||
|
||||
///-------------------------------------------
|
||||
/// @name Inferring Identifiers from the Model
|
||||
///-------------------------------------------
|
||||
|
||||
// NOTE: Add not about checking the entity's userInfo
|
||||
+ (RKEntityIdentifier *)inferredIdentifierForEntity:(NSEntityDescription *)entity;
|
||||
|
||||
@end
|
||||
117
Code/CoreData/RKEntityIdentifier.m
Normal file
117
Code/CoreData/RKEntityIdentifier.m
Normal file
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// RKEntityIdentifier.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 11/20/12.
|
||||
// Copyright (c) 2012 RestKit. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKEntityIdentifier.h"
|
||||
#import "RKManagedObjectStore.h"
|
||||
|
||||
NSString * const RKEntityIdentifierUserInfoKey = @"RKEntityIdentifierAttributes";
|
||||
|
||||
static NSArray *RKEntityIdentifierAttributesFromUserInfoOfEntity(NSEntityDescription *entity)
|
||||
{
|
||||
id userInfoValue = [entity userInfo][RKEntityIdentifierUserInfoKey];
|
||||
if (userInfoValue) {
|
||||
NSArray *attributeNames = [userInfoValue isKindOfClass:[NSArray class]] ? userInfoValue : @[ userInfoValue ];
|
||||
NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:[attributeNames count]];
|
||||
[attributeNames enumerateObjectsUsingBlock:^(NSString *attributeName, NSUInteger idx, BOOL *stop) {
|
||||
if (! [attributeName isKindOfClass:[NSString class]]) {
|
||||
[NSException raise:NSInvalidArgumentException format:@"Invalid value given in user info key '%@' of entity '%@': expected an `NSString` or `NSArray` of strings, instead got '%@' (%@)", RKEntityIdentifierUserInfoKey, [entity name], attributeName, [attributeName class]];
|
||||
}
|
||||
|
||||
NSAttributeDescription *attribute = [entity attributesByName][attributeName];
|
||||
if (! attribute) {
|
||||
[NSException raise:NSInvalidArgumentException format:@"Invalid identifier attribute specified in user info key '%@' of entity '%@': no attribue was found with the name '%@'", RKEntityIdentifierUserInfoKey, [entity name], attributeName];
|
||||
}
|
||||
|
||||
[attributes addObject:attribute];
|
||||
}];
|
||||
return attributes;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Given 'Human', returns 'humanID'; Given 'AmenityReview' returns 'amenityReviewID'
|
||||
static NSString *RKEntityIdentifierAttributeNameForEntity(NSEntityDescription *entity)
|
||||
{
|
||||
NSString *entityName = [entity name];
|
||||
NSString *lowerCasedFirstCharacter = [[entityName substringToIndex:1] lowercaseString];
|
||||
return [NSString stringWithFormat:@"%@%@ID", lowerCasedFirstCharacter, [entityName substringFromIndex:1]];
|
||||
}
|
||||
|
||||
static NSArray *RKEntityIdentifierAttributeNames()
|
||||
{
|
||||
return [NSArray arrayWithObjects:@"identifier", @"ID", @"URL", @"url", nil];
|
||||
}
|
||||
|
||||
static NSArray *RKArrayOfAttributesForEntityFromAttributesOrNames(NSEntityDescription *entity, NSArray *attributesOrNames)
|
||||
{
|
||||
NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:[attributesOrNames count]];
|
||||
for (id attributeOrName in attributesOrNames) {
|
||||
if ([attributeOrName isKindOfClass:[NSAttributeDescription class]]) {
|
||||
if (! [[entity properties] containsObject:attributeOrName]) [NSException raise:NSInvalidArgumentException format:@"Invalid attribute value '%@' given for entity identifer: not found in the '%@' entity", attributeOrName, [entity name]];
|
||||
[attributes addObject:attributeOrName];
|
||||
} else if ([attributeOrName isKindOfClass:[NSString class]]) {
|
||||
NSAttributeDescription *attribute = [entity attributesByName][attributeOrName];
|
||||
if (!attribute) [NSException raise:NSInvalidArgumentException format:@"Invalid attribute '%@': no attribute was found for the given name in the '%@' entity.", attributeOrName, [entity name]];
|
||||
[attributes addObject:attribute];
|
||||
} else {
|
||||
[NSException raise:NSInvalidArgumentException format:@"Invalid value provided for entity identifier attribute: Acceptable values are either `NSAttributeDescription` or `NSString` objects."];
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@interface RKEntityIdentifier ()
|
||||
@property (nonatomic, strong, readwrite) NSEntityDescription *entity;
|
||||
@property (nonatomic, copy, readwrite) NSArray *attributes;
|
||||
@end
|
||||
|
||||
@implementation RKEntityIdentifier
|
||||
|
||||
+ (id)identifierWithEntityName:(NSString *)entityName attributes:(NSArray *)attributes inManagedObjectStore:(RKManagedObjectStore *)managedObjectStore
|
||||
{
|
||||
NSEntityDescription *entity = [managedObjectStore.managedObjectModel entitiesByName][entityName];
|
||||
return [[self alloc] initWithEntity:entity attributes:attributes];
|
||||
}
|
||||
|
||||
- (id)initWithEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributes
|
||||
{
|
||||
NSParameterAssert(entity);
|
||||
NSParameterAssert(attributes);
|
||||
NSAssert([attributes count], @"At least one attribute must be provided to identify managed objects");
|
||||
self = [self init];
|
||||
if (self) {
|
||||
self.entity = entity;
|
||||
self.attributes = RKArrayOfAttributesForEntityFromAttributesOrNames(entity, attributes);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Idetifier Inference
|
||||
|
||||
+ (RKEntityIdentifier *)inferredIdentifierForEntity:(NSEntityDescription *)entity
|
||||
{
|
||||
NSArray *attributes = RKEntityIdentifierAttributesFromUserInfoOfEntity(entity);
|
||||
if (attributes) {
|
||||
return [[RKEntityIdentifier alloc] initWithEntity:entity attributes:attributes];
|
||||
}
|
||||
|
||||
NSMutableArray *identifyingAttributes = [NSMutableArray arrayWithObject:RKEntityIdentifierAttributeNameForEntity(entity)];
|
||||
[identifyingAttributes addObjectsFromArray:RKEntityIdentifierAttributeNames()];
|
||||
for (NSString *attributeName in identifyingAttributes) {
|
||||
NSAttributeDescription *attribute = [entity attributesByName][attributeName];
|
||||
if (attribute) {
|
||||
return [[RKEntityIdentifier alloc] initWithEntity:entity attributes:@[ attribute ]];
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -20,13 +20,26 @@
|
||||
|
||||
#import <CoreData/CoreData.h>
|
||||
#import "RKObjectMapping.h"
|
||||
#import "RKConnectionMapping.h"
|
||||
#import "RKConnectionDescription.h"
|
||||
#import "RKMacros.h"
|
||||
#import "RKEntityIdentifier.h"
|
||||
|
||||
@class RKManagedObjectStore;
|
||||
|
||||
/**
|
||||
RKEntityMapping objects model an object mapping with a Core Data destination entity.
|
||||
|
||||
## Entity Identification
|
||||
|
||||
TBD
|
||||
|
||||
### Inferring Entity Identifiers
|
||||
|
||||
TBD
|
||||
|
||||
## Connecting Relationships
|
||||
|
||||
TBD
|
||||
*/
|
||||
@interface RKEntityMapping : RKObjectMapping
|
||||
|
||||
@@ -62,58 +75,63 @@
|
||||
*/
|
||||
@property (nonatomic, strong) NSEntityDescription *entity;
|
||||
|
||||
/**
|
||||
The name of the attribute on the destination entity that acts as the primary key for instances
|
||||
of the entity in the remote backend system. Used to uniquely identify objects within the store
|
||||
so that existing objects are updated rather than creating new ones.
|
||||
// The index for finding existing objects. Can be compound.
|
||||
@property (nonatomic, copy) RKEntityIdentifier *entityIdentifier;
|
||||
|
||||
@warning Note that primaryKeyAttribute defaults to the primaryKeyAttribute configured
|
||||
on the NSEntityDescription for the entity targetted by the receiving mapping. This provides
|
||||
flexibility in cases where a single entity is the target of many mappings with differing
|
||||
primary key definitions.
|
||||
// Setting an existing entity identifier replaces it...
|
||||
- (void)setEntityIdentifier:(RKEntityIdentifier *)entityIdentifier forRelationship:(NSString *)relationshipName;
|
||||
|
||||
If the `primaryKeyAttribute` is set on an `RKEntityMapping` that targets an entity with a
|
||||
nil primaryKeyAttribute, then the primaryKeyAttribute will be set on the entity as well for
|
||||
convenience and backwards compatibility. This may change in the future.
|
||||
// NOTE: This returns explicitly set value, then the entityIdentifier for the relationship mapping
|
||||
- (RKEntityIdentifier *)entityIdentifierForRelationship:(NSString *)relationshipName;
|
||||
|
||||
@see `[NSEntityDescription primaryKeyAttribute]`
|
||||
*/
|
||||
// TODO: Make me readonly
|
||||
@property (nonatomic, strong) NSString *primaryKeyAttribute;
|
||||
///**
|
||||
// The name of the attribute on the destination entity that acts as the primary key for instances
|
||||
// of the entity in the remote backend system. Used to uniquely identify objects within the store
|
||||
// so that existing objects are updated rather than creating new ones.
|
||||
//
|
||||
// @warning Note that primaryKeyAttribute defaults to the primaryKeyAttribute configured
|
||||
// on the NSEntityDescription for the entity targetted by the receiving mapping. This provides
|
||||
// flexibility in cases where a single entity is the target of many mappings with differing
|
||||
// primary key definitions.
|
||||
//
|
||||
// If the `primaryKeyAttribute` is set on an `RKEntityMapping` that targets an entity with a
|
||||
// nil primaryKeyAttribute, then the primaryKeyAttribute will be set on the entity as well for
|
||||
// convenience and backwards compatibility. This may change in the future.
|
||||
//
|
||||
// @see `[NSEntityDescription primaryKeyAttribute]`
|
||||
// */
|
||||
//// TODO: Make me readonly
|
||||
//@property (nonatomic, strong) NSString *primaryKeyAttribute;
|
||||
|
||||
/**
|
||||
Retrieves an array of RKConnectionMapping objects for connecting the receiver's relationships
|
||||
by primary key.
|
||||
|
||||
@see `RKConnectionMapping`
|
||||
*/
|
||||
@property (weak, nonatomic, readonly) NSArray *connectionMappings;
|
||||
@property (weak, nonatomic, readonly) NSArray *connections;
|
||||
|
||||
/**
|
||||
Adds a connection mapping to the receiver.
|
||||
|
||||
@param connectionMapping The connection mapping to be added.
|
||||
*/
|
||||
- (void)addConnectionMapping:(RKConnectionMapping *)connectionMapping;
|
||||
- (void)addConnectionMappingsFromArray:(NSArray *)arrayOfConnectionMappings;
|
||||
- (void)addConnection:(RKConnectionDescription *)connection;
|
||||
- (void)removeConnection:(RKConnectionDescription *)connection;
|
||||
- (void)addConnectionForRelationship:(id)relationshipOrName connectedBy:(id)connectionSpecifier;
|
||||
- (RKConnectionDescription *)connectionForRelationship:(id)relationshipOrName;
|
||||
|
||||
// Convenience method.
|
||||
- (RKConnectionMapping *)addConnectionMappingForRelationshipForName:(NSString *)relationshipName
|
||||
fromSourceKeyPath:(NSString *)sourceKeyPath
|
||||
toKeyPath:(NSString *)destinationKeyPath
|
||||
matcher:(RKDynamicMappingMatcher *)matcher;
|
||||
|
||||
/**
|
||||
Removes a connection mapping from the receiver.
|
||||
|
||||
@param connectionMapping The connection mapping to be added.
|
||||
*/
|
||||
- (void)removeConnectionMapping:(RKConnectionMapping *)connectionMapping;
|
||||
///------------------------------------------
|
||||
/// @name Retrieving Default Attribute Values
|
||||
///------------------------------------------
|
||||
|
||||
/**
|
||||
Returns the default value for the specified attribute as expressed in the Core Data entity definition. This value will
|
||||
be assigned if the object mapping is applied and a value for a missing attribute is not present in the payload.
|
||||
*/
|
||||
- (id)defaultValueForMissingAttribute:(NSString *)attributeName;
|
||||
- (id)defaultValueForAttribute:(NSString *)attributeName;
|
||||
|
||||
///----------------------------------------------
|
||||
/// @name Configuring Entity Identifier Inference
|
||||
///----------------------------------------------
|
||||
|
||||
// setInfersEntityIdentifiers:(BOOL) | setShouldInferEntityIdentifiers:
|
||||
// setEntityIdentificationInferenceEnabled: | :
|
||||
+ (void)setEntityIdentifierInferenceEnabled:(BOOL)enabled; // Default YES
|
||||
+ (BOOL)isEntityIdentifierInferenceEnabled;
|
||||
|
||||
@end
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#import "RKManagedObjectStore.h"
|
||||
#import "RKDynamicMappingMatcher.h"
|
||||
#import "RKPropertyInspector+CoreData.h"
|
||||
#import "NSEntityDescription+RKAdditions.h"
|
||||
#import "RKLog.h"
|
||||
#import "RKRelationshipMapping.h"
|
||||
#import "RKObjectUtilities.h"
|
||||
@@ -31,14 +30,33 @@
|
||||
#undef RKLogComponent
|
||||
#define RKLogComponent RKlcl_cRestKitCoreData
|
||||
|
||||
static BOOL entityIdentifierInferenceEnabled = YES;
|
||||
|
||||
static void RKInferIdentifiersForEntityMapping(RKEntityMapping *entityMapping)
|
||||
{
|
||||
if (! [RKEntityMapping isEntityIdentifierInferenceEnabled]) return;
|
||||
|
||||
entityMapping.entityIdentifier = [RKEntityIdentifier inferredIdentifierForEntity:entityMapping.entity];
|
||||
[[entityMapping.entity relationshipsByName] enumerateKeysAndObjectsUsingBlock:^(NSString *relationshipName, NSRelationshipDescription *relationship, BOOL *stop) {
|
||||
RKEntityIdentifier *entityIdentififer = [RKEntityIdentifier inferredIdentifierForEntity:relationship.destinationEntity];
|
||||
if (entityIdentififer) {
|
||||
[entityMapping setEntityIdentifier:entityIdentififer forRelationship:relationshipName];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@interface RKObjectMapping (Private)
|
||||
- (NSString *)transformSourceKeyPath:(NSString *)keyPath;
|
||||
@end
|
||||
|
||||
@interface RKEntityMapping ()
|
||||
@property (nonatomic, weak, readwrite) Class objectClass;
|
||||
@property (nonatomic, strong) NSMutableArray *mutableConnections;
|
||||
@property (nonatomic, strong) NSMutableDictionary *relationshipNamesToEntityIdentifiers;
|
||||
@end
|
||||
|
||||
@implementation RKEntityMapping
|
||||
|
||||
|
||||
+ (id)mappingForClass:(Class)objectClass
|
||||
{
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
@@ -60,9 +78,7 @@
|
||||
self = [self initWithClass:objectClass];
|
||||
if (self) {
|
||||
self.entity = entity;
|
||||
|
||||
[self addObserver:self forKeyPath:@"entity" options:NSKeyValueObservingOptionInitial context:nil];
|
||||
[self addObserver:self forKeyPath:@"primaryKeyAttribute" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil];
|
||||
RKInferIdentifiersForEntityMapping(self);
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -73,61 +89,106 @@
|
||||
self = [super initWithClass:objectClass];
|
||||
if (self) {
|
||||
self.mutableConnections = [NSMutableArray array];
|
||||
self.relationshipNamesToEntityIdentifiers = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
[self removeObserver:self forKeyPath:@"entity"];
|
||||
[self removeObserver:self forKeyPath:@"primaryKeyAttribute"];
|
||||
RKEntityMapping *copy = [super copyWithZone:zone];
|
||||
copy.entityIdentifier = [self.entityIdentifier copy];
|
||||
|
||||
for (RKConnectionDescription *connection in self.connections) {
|
||||
[copy addConnection:[connection copy]];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
- (RKConnectionMapping *)connectionMappingForRelationshipWithName:(NSString *)relationshipName
|
||||
- (RKConnectionDescription *)connectionForRelationship:(id)relationshipOrName
|
||||
{
|
||||
for (RKConnectionMapping *connection in self.connectionMappings) {
|
||||
if ([connection.relationship.name isEqualToString:relationshipName]) {
|
||||
NSAssert([relationshipOrName isKindOfClass:[NSString class]] || [relationshipOrName isKindOfClass:[NSRelationshipDescription class]], @"Relationship specifier must be a name or a relationship description");
|
||||
NSString *relationshipName = [relationshipOrName isKindOfClass:[NSRelationshipDescription class]] ? [(NSRelationshipDescription *)relationshipOrName name] : relationshipOrName;
|
||||
for (RKConnectionDescription *connection in self.connections) {
|
||||
if ([[connection.relationship name] isEqualToString:relationshipName]) {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)addConnectionMapping:(RKConnectionMapping *)mapping
|
||||
- (void)addConnection:(RKConnectionDescription *)connection
|
||||
{
|
||||
NSParameterAssert(mapping);
|
||||
RKConnectionMapping *connectionMapping = [self connectionMappingForRelationshipWithName:mapping.relationship.name];
|
||||
NSAssert(connectionMapping == nil, @"Cannot add connect relationship %@ by primary key, a mapping already exists.", mapping.relationship.name);
|
||||
NSParameterAssert(connection);
|
||||
RKConnectionDescription *existingConnection = [self connectionForRelationship:connection.relationship];
|
||||
NSAssert(existingConnection == nil, @"Cannot add connection: An existing connection already exists for the '%@' relationship.", connection.relationship.name);
|
||||
NSAssert(self.mutableConnections, @"self.mutableConnections should not be nil");
|
||||
[self.mutableConnections addObject:mapping];
|
||||
[self.mutableConnections addObject:connection];
|
||||
}
|
||||
|
||||
- (void)addConnectionMappingsFromArray:(NSArray *)arrayOfConnectionMappings
|
||||
- (void)removeConnection:(RKConnectionDescription *)connection
|
||||
{
|
||||
for (RKConnectionMapping *connectionMapping in arrayOfConnectionMappings) {
|
||||
[self addConnectionMapping:connectionMapping];
|
||||
[self.mutableConnections removeObject:connection];
|
||||
}
|
||||
|
||||
- (NSArray *)connections
|
||||
{
|
||||
return [NSArray arrayWithArray:self.mutableConnections];
|
||||
}
|
||||
|
||||
- (void)addConnectionForRelationship:(id)relationshipOrName connectedBy:(id)connectionSpecifier
|
||||
{
|
||||
NSRelationshipDescription *relationship = [relationshipOrName isKindOfClass:[NSRelationshipDescription class]] ? relationshipOrName : [self.entity relationshipsByName][relationshipOrName];
|
||||
NSAssert(relationship, @"No relatiobship was found named '%@' in the '%@' entity", relationshipOrName, [self.entity name]);
|
||||
RKConnectionDescription *connection = nil;
|
||||
if ([connectionSpecifier isKindOfClass:[NSString class]]) {
|
||||
NSString *sourceAttribute = connectionSpecifier;
|
||||
NSString *destinationAttribute = [self transformSourceKeyPath:sourceAttribute];
|
||||
connection = [[RKConnectionDescription alloc] initWithRelationship:relationship attributes:@{ sourceAttribute: destinationAttribute }];
|
||||
} else if ([connectionSpecifier isKindOfClass:[NSArray class]]) {
|
||||
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:[connectionSpecifier count]];
|
||||
for (NSString *sourceAttribute in connectionSpecifier) {
|
||||
NSString *destinationAttribute = [self transformSourceKeyPath:sourceAttribute];
|
||||
attributes[sourceAttribute] = destinationAttribute;
|
||||
}
|
||||
connection = [[RKConnectionDescription alloc] initWithRelationship:relationship attributes:attributes];
|
||||
} else if ([connectionSpecifier isKindOfClass:[NSDictionary class]]) {
|
||||
connection = [[RKConnectionDescription alloc] initWithRelationship:relationship attributes:connectionSpecifier];
|
||||
} else {
|
||||
[NSException raise:NSInvalidArgumentException format:@"Connections can only be described using `NSString`, `NSArray`, or `NSDictionary` objects. Instead, got: %@", connectionSpecifier];
|
||||
}
|
||||
|
||||
[self.mutableConnections addObject:connection];
|
||||
}
|
||||
|
||||
- (RKConnectionMapping *)addConnectionMappingForRelationshipForName:(NSString *)relationshipName
|
||||
fromSourceKeyPath:(NSString *)sourceKeyPath
|
||||
toKeyPath:(NSString *)destinationKeyPath
|
||||
matcher:(RKDynamicMappingMatcher *)matcher
|
||||
- (void)setEntityIdentifier:(RKEntityIdentifier *)entityIdentifier
|
||||
{
|
||||
NSRelationshipDescription *relationship = [[self.entity propertiesByName] objectForKey:relationshipName];
|
||||
NSAssert(relationship, @"Unable to find a relationship named '%@' in the entity: %@", relationshipName, self.entity);
|
||||
RKConnectionMapping *connectionMapping = [[RKConnectionMapping alloc] initWithRelationship:relationship sourceKeyPath:sourceKeyPath destinationKeyPath:destinationKeyPath matcher:matcher];
|
||||
[self addConnectionMapping:connectionMapping];
|
||||
return connectionMapping;
|
||||
NSAssert(entityIdentifier == nil || [entityIdentifier.entity isKindOfEntity:self.entity], @"Invalid entity identifier value: The identifier given is for the '%@' entity.", [entityIdentifier.entity name]);
|
||||
_entityIdentifier = entityIdentifier;
|
||||
}
|
||||
|
||||
- (void)removeConnectionMapping:(RKConnectionMapping *)connectionMapping
|
||||
- (void)setEntityIdentifier:(RKEntityIdentifier *)entityIdentifier forRelationship:(NSString *)relationshipName
|
||||
{
|
||||
[self.mutableConnections removeObject:connectionMapping];
|
||||
NSRelationshipDescription *relationship = [self.entity relationshipsByName][relationshipName];
|
||||
NSAssert(relationship, @"Cannot set entity identififer for relationship '%@': no relationship found for that name.", relationshipName);
|
||||
NSAssert([[relationship destinationEntity] isKindOfEntity:entityIdentifier.entity], @"Cannot set entity identifier for relationship '%@': the given relationship identifier is for the '%@' entity, but the '%@' entity was expected.", relationshipName, [entityIdentifier.entity name], [[relationship destinationEntity] name]);
|
||||
self.relationshipNamesToEntityIdentifiers[relationshipName] = entityIdentifier;
|
||||
}
|
||||
|
||||
- (id)defaultValueForMissingAttribute:(NSString *)attributeName
|
||||
- (RKEntityIdentifier *)entityIdentifierForRelationship:(NSString *)relationshipName
|
||||
{
|
||||
RKEntityIdentifier *entityIdentifier = self.relationshipNamesToEntityIdentifiers[relationshipName];
|
||||
if (! entityIdentifier) {
|
||||
RKRelationshipMapping *relationshipMapping = [self propertyMappingsByDestinationKeyPath][relationshipName];
|
||||
entityIdentifier = [relationshipMapping.mapping isKindOfClass:[RKEntityIdentifier class]] ? [(RKEntityMapping *)relationshipMapping.mapping entityIdentifier] : nil;
|
||||
}
|
||||
|
||||
return entityIdentifier;
|
||||
}
|
||||
|
||||
- (id)defaultValueForAttribute:(NSString *)attributeName
|
||||
{
|
||||
NSAttributeDescription *desc = [[self.entity attributesByName] valueForKey:attributeName];
|
||||
return [desc defaultValue];
|
||||
@@ -143,25 +204,14 @@
|
||||
return propertyClass;
|
||||
}
|
||||
|
||||
/*
|
||||
Allows the primaryKeyAttributeName property on the NSEntityDescription to configure the mapping and vice-versa
|
||||
*/
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||
+ (void)setEntityIdentifierInferenceEnabled:(BOOL)enabled
|
||||
{
|
||||
if ([keyPath isEqualToString:@"entity"]) {
|
||||
if (! self.primaryKeyAttribute) {
|
||||
self.primaryKeyAttribute = [self.entity primaryKeyAttributeName];
|
||||
}
|
||||
} else if ([keyPath isEqualToString:@"primaryKeyAttribute"]) {
|
||||
if (! self.entity.primaryKeyAttribute) {
|
||||
self.entity.primaryKeyAttributeName = self.primaryKeyAttribute;
|
||||
}
|
||||
}
|
||||
entityIdentifierInferenceEnabled = enabled;
|
||||
}
|
||||
|
||||
- (NSArray *)connectionMappings
|
||||
+ (BOOL)isEntityIdentifierInferenceEnabled
|
||||
{
|
||||
return [NSArray arrayWithArray:self.mutableConnections];
|
||||
return entityIdentifierInferenceEnabled;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
#import "RKFetchRequestManagedObjectCache.h"
|
||||
#import "NSEntityDescription+RKAdditions.h"
|
||||
#import "RKLog.h"
|
||||
#import "RKPropertyInspector.h"
|
||||
#import "RKPropertyInspector+CoreData.h"
|
||||
@@ -16,37 +15,56 @@
|
||||
#undef RKLogComponent
|
||||
#define RKLogComponent RKlcl_cRestKitCoreData
|
||||
|
||||
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)
|
||||
{
|
||||
NSMutableArray *formatFragments = [NSMutableArray arrayWithCapacity:[attributeNames count]];
|
||||
for (NSString *attributeName in attributeNames) {
|
||||
NSString *formatFragment = [NSString stringWithFormat:@"%@ = $%@", attributeName, attributeName];
|
||||
[formatFragments addObject:formatFragment];
|
||||
}
|
||||
|
||||
return [NSPredicate predicateWithFormat:[formatFragments componentsJoinedByString:@" AND "]];
|
||||
}
|
||||
|
||||
@interface RKFetchRequestManagedObjectCache ()
|
||||
@property (nonatomic, strong) NSCache *predicateCache;
|
||||
@end
|
||||
|
||||
@implementation RKFetchRequestManagedObjectCache
|
||||
|
||||
- (NSArray *)findInstancesOfEntity:(NSEntityDescription *)entity
|
||||
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
|
||||
value:(id)primaryKeyValue
|
||||
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.predicateCache = [NSCache new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity
|
||||
attributeValues:(NSDictionary *)attributeValues
|
||||
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
|
||||
{
|
||||
|
||||
NSAssert(entity, @"Cannot find existing managed object without a target class");
|
||||
NSAssert(primaryKeyAttribute, @"Cannot find existing managed object instance without mapping that defines a primaryKeyAttribute");
|
||||
NSAssert(attributeValues, @"Cannot retrieve cached objects without attribute values to identify them with.");
|
||||
NSAssert(managedObjectContext, @"Cannot find existing managed object with a nil context");
|
||||
|
||||
id searchValue = primaryKeyValue;
|
||||
Class type = [[RKPropertyInspector sharedInspector] classForPropertyNamed:primaryKeyAttribute ofEntity:entity];
|
||||
if (type && ([type isSubclassOfClass:[NSString class]] && NO == [primaryKeyValue isKindOfClass:[NSString class]])) {
|
||||
searchValue = [NSString stringWithFormat:@"%@", primaryKeyValue];
|
||||
} else if (type && ([type isSubclassOfClass:[NSNumber class]] && NO == [primaryKeyValue isKindOfClass:[NSNumber class]])) {
|
||||
if ([primaryKeyValue isKindOfClass:[NSString class]]) {
|
||||
searchValue = [NSNumber numberWithDouble:[(NSString *)primaryKeyValue doubleValue]];
|
||||
}
|
||||
}
|
||||
|
||||
// Use cached predicate if primary key matches
|
||||
NSPredicate *predicate = nil;
|
||||
if ([entity.primaryKeyAttributeName isEqualToString:primaryKeyAttribute]) {
|
||||
predicate = [entity predicateForPrimaryKeyAttributeWithValue:searchValue];
|
||||
} else {
|
||||
// Parse a predicate
|
||||
predicate = [NSPredicate predicateWithFormat:@"%K = %@", primaryKeyAttribute, searchValue];
|
||||
|
||||
NSString *predicateCacheKey = RKPredicateCacheKeyForAttributes([attributeValues allKeys]);
|
||||
NSPredicate *substitutionPredicate = [self.predicateCache objectForKey:predicateCacheKey];
|
||||
if (! substitutionPredicate) {
|
||||
substitutionPredicate = RKPredicateWithSubsitutionVariablesForAttributes([attributeValues allKeys]);
|
||||
[self.predicateCache setObject:substitutionPredicate forKey:predicateCacheKey];
|
||||
}
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:[entity name]];
|
||||
fetchRequest.predicate = predicate;
|
||||
fetchRequest.predicate = [substitutionPredicate predicateWithSubstitutionVariables:attributeValues];
|
||||
NSError *error = nil;
|
||||
NSArray *objects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
||||
if (! objects) {
|
||||
@@ -57,18 +75,4 @@
|
||||
return objects;
|
||||
}
|
||||
|
||||
- (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
|
||||
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
|
||||
value:(id)primaryKeyValue
|
||||
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
|
||||
{
|
||||
NSArray *objects = [self findInstancesOfEntity:entity withPrimaryKeyAttribute:primaryKeyAttribute value:primaryKeyValue inManagedObjectContext:managedObjectContext];
|
||||
|
||||
NSManagedObject *object = nil;
|
||||
if ([objects count] > 0) {
|
||||
object = [objects objectAtIndex:0];
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -52,38 +52,23 @@
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
|
||||
- (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
|
||||
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
|
||||
value:(id)primaryKeyValue
|
||||
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
|
||||
- (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity
|
||||
attributeValues:(NSDictionary *)attributeValues
|
||||
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
|
||||
{
|
||||
NSAssert(self.entityCache, @"Entity cache cannot be nil.");
|
||||
if (! [self.entityCache isEntity:entity cachedByAttribute:primaryKeyAttribute]) {
|
||||
RKLogInfo(@"Caching instances of Entity '%@' by primary key attribute '%@'", entity.name, primaryKeyAttribute);
|
||||
[self.entityCache cacheObjectsForEntity:entity byAttribute:primaryKeyAttribute];
|
||||
RKEntityByAttributeCache *attributeCache = [self.entityCache attributeCacheForEntity:entity attribute:primaryKeyAttribute];
|
||||
NSParameterAssert(entity);
|
||||
NSParameterAssert(attributeValues);
|
||||
NSParameterAssert(managedObjectContext);
|
||||
|
||||
NSArray *attributes = [attributeValues allKeys];
|
||||
if (! [self.entityCache isEntity:entity cachedByAttributes:attributes]) {
|
||||
RKLogInfo(@"Caching instances of Entity '%@' by attributes '%@'", entity.name, [attributes componentsJoinedByString:@", "]);
|
||||
[self.entityCache cacheObjectsForEntity:entity byAttributes:attributes];
|
||||
RKEntityByAttributeCache *attributeCache = [self.entityCache attributeCacheForEntity:entity attributes:attributes];
|
||||
RKLogTrace(@"Cached %ld objects", (long)[attributeCache count]);
|
||||
}
|
||||
|
||||
return [self.entityCache objectForEntity:entity withAttribute:primaryKeyAttribute value:primaryKeyValue inContext:managedObjectContext];
|
||||
}
|
||||
|
||||
- (NSArray *)findInstancesOfEntity:(NSEntityDescription *)entity
|
||||
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
|
||||
value:(id)primaryKeyValue
|
||||
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
|
||||
{
|
||||
NSAssert(self.entityCache, @"Entity cache cannot be nil.");
|
||||
|
||||
if (! [self.entityCache isEntity:entity cachedByAttribute:primaryKeyAttribute]) {
|
||||
RKLogInfo(@"Caching instances of Entity '%@' by primary key attribute '%@'", entity.name, primaryKeyAttribute);
|
||||
[self.entityCache cacheObjectsForEntity:entity byAttribute:primaryKeyAttribute];
|
||||
RKEntityByAttributeCache *attributeCache = [self.entityCache attributeCacheForEntity:entity attribute:primaryKeyAttribute];
|
||||
RKLogTrace(@"Cached %ld objects", (long)[attributeCache count]);
|
||||
}
|
||||
|
||||
return [self.entityCache objectsForEntity:entity withAttribute:primaryKeyAttribute value:primaryKeyValue inContext:managedObjectContext];
|
||||
|
||||
return [self.entityCache objectsForEntity:entity withAttributeValues:attributeValues inContext:managedObjectContext];
|
||||
}
|
||||
|
||||
- (void)didFetchObject:(NSManagedObject *)object
|
||||
|
||||
@@ -18,6 +18,13 @@
|
||||
|
||||
@required
|
||||
|
||||
/// @name Retrieving Managed Objects
|
||||
|
||||
// New API
|
||||
- (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity
|
||||
attributeValues:(NSDictionary *)attributeValues
|
||||
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
|
||||
|
||||
///------------------------------
|
||||
/// @name Finding Managed Objects
|
||||
///------------------------------
|
||||
@@ -33,10 +40,10 @@
|
||||
@return A managed object that is an instance of the given entity with a primary key and value matching
|
||||
the specified parameters, or nil if no object was found.
|
||||
*/
|
||||
- (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
|
||||
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
|
||||
value:(id)primaryKeyValue
|
||||
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
|
||||
//- (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
|
||||
// withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
|
||||
// value:(id)primaryKeyValue
|
||||
// inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
|
||||
|
||||
/**
|
||||
Retrieves an array of model objects from the object store given a Core Data entity and
|
||||
@@ -49,10 +56,10 @@
|
||||
@return An array of managed objects that are instances of the given entity with a primary key and value matching
|
||||
the specified parameters, or nil if no object was found.
|
||||
*/
|
||||
- (NSArray *)findInstancesOfEntity:(NSEntityDescription *)entity
|
||||
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
|
||||
value:(id)primaryKeyValue
|
||||
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
|
||||
//- (NSArray *)findInstancesOfEntity:(NSEntityDescription *)entity
|
||||
// withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
|
||||
// value:(id)primaryKeyValue
|
||||
// inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
|
||||
|
||||
///---------------------------------------------------
|
||||
/// @name Handling Managed Object Change Notifications
|
||||
|
||||
@@ -26,8 +26,94 @@
|
||||
#import "RKMappingOperation.h"
|
||||
#import "RKDynamicMappingMatcher.h"
|
||||
#import "RKManagedObjectCaching.h"
|
||||
#import "RKRelationshipConnectionOperation.h"
|
||||
#import "RKConnectionOperation.h"
|
||||
#import "RKMappingErrors.h"
|
||||
#import "RKValueTransformers.h"
|
||||
|
||||
extern NSString * const RKObjectMappingNestingAttributeKeyName;
|
||||
|
||||
id RKTransformedValueWithClass(id value, Class destinationType, NSValueTransformer *dateToStringValueTransformer);
|
||||
NSArray *RKApplyNestingAttributeValueToMappings(NSString *attributeName, id value, NSArray *propertyMappings);
|
||||
|
||||
// Return YES if the entity is identified by an attribute that acts as the nesting key in the source representation
|
||||
static BOOL RKEntityMappingIsIdentifiedByNestingAttribute(RKEntityMapping *entityMapping)
|
||||
{
|
||||
for (NSAttributeDescription *attribute in entityMapping.entityIdentifier.attributes) {
|
||||
RKAttributeMapping *attributeMapping = [[entityMapping propertyMappingsByDestinationKeyPath] objectForKey:[attribute name]];
|
||||
if ([attributeMapping.sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
// We always need to map the dynamic nesting attribute first so that sub-key attribute mappings apply cleanly
|
||||
static NSArray *RKEntityIdentifierAttributesInMappingOrder(RKEntityMapping *entityMapping)
|
||||
{
|
||||
NSMutableArray *orderedAttributes = [NSMutableArray arrayWithCapacity:[entityMapping.entityIdentifier.attributes count]];
|
||||
for (NSAttributeDescription *attribute in entityMapping.entityIdentifier.attributes) {
|
||||
RKAttributeMapping *attributeMapping = [[entityMapping propertyMappingsByDestinationKeyPath] objectForKey:[attribute name]];
|
||||
if ([attributeMapping.sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) {
|
||||
// We want to map the nesting attribute first
|
||||
[orderedAttributes insertObject:attribute atIndex:0];
|
||||
} else {
|
||||
[orderedAttributes addObject:attribute];
|
||||
}
|
||||
}
|
||||
|
||||
return orderedAttributes;
|
||||
}
|
||||
|
||||
static id RKValueForAttributeMappingInRepresentation(RKAttributeMapping *attributeMapping, NSDictionary *representation)
|
||||
{
|
||||
if ([attributeMapping.sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) {
|
||||
return [[representation allKeys] lastObject];
|
||||
} else {
|
||||
return [representation valueForKeyPath:attributeMapping.sourceKeyPath];
|
||||
}
|
||||
}
|
||||
|
||||
static RKAttributeMapping *RKAttributeMappingForNameInMappings(NSString *name, NSArray *attributeMappings)
|
||||
{
|
||||
for (RKAttributeMapping *attributeMapping in attributeMappings) {
|
||||
if ([[attributeMapping destinationKeyPath] isEqualToString:name]) return attributeMapping;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/**
|
||||
This function is the workhorse for extracting entity identifier attributes from a dictionary representation. It supports type transformations, compound entity identifier attributes, and dynamic nesting keys within the representation.
|
||||
*/
|
||||
static NSDictionary *RKEntityIdentifierAttributesForEntityMappingWithRepresentation(RKEntityMapping *entityMapping, NSDictionary *representation)
|
||||
{
|
||||
RKDateToStringValueTransformer *dateToStringTransformer = [[RKDateToStringValueTransformer alloc] initWithDateToStringFormatter:entityMapping.preferredDateFormatter
|
||||
stringToDateFormatters:entityMapping.dateFormatters];
|
||||
NSArray *orderedAttributes = RKEntityIdentifierAttributesInMappingOrder(entityMapping);
|
||||
BOOL containsNestingAttribute = RKEntityMappingIsIdentifiedByNestingAttribute(entityMapping);
|
||||
__block NSArray *attributeMappings = entityMapping.attributeMappings;
|
||||
if (containsNestingAttribute) RKLogDebug(@"Detected use of nested dictionary key as identifying attribute");
|
||||
|
||||
NSMutableDictionary *entityIdentifierAttributes = [NSMutableDictionary dictionaryWithCapacity:[orderedAttributes count]];
|
||||
[orderedAttributes enumerateObjectsUsingBlock:^(NSAttributeDescription *attribute, NSUInteger idx, BOOL *stop) {
|
||||
RKAttributeMapping *attributeMapping = RKAttributeMappingForNameInMappings([attribute name], attributeMappings);
|
||||
Class attributeClass = [entityMapping classForProperty:[attribute name]];
|
||||
id attributeValue = nil;
|
||||
if (containsNestingAttribute && idx == 0) {
|
||||
// This is the nesting attribute
|
||||
attributeValue = RKTransformedValueWithClass([[representation allKeys] lastObject], attributeClass, dateToStringTransformer);
|
||||
attributeMappings = RKApplyNestingAttributeValueToMappings([attribute name], attributeValue, attributeMappings);
|
||||
} else {
|
||||
id sourceValue = RKValueForAttributeMappingInRepresentation(attributeMapping, representation);
|
||||
attributeValue = RKTransformedValueWithClass(sourceValue, attributeClass, dateToStringTransformer);
|
||||
}
|
||||
|
||||
[entityIdentifierAttributes setObject:attributeValue ?: [NSNull null] forKey:[attribute name]];
|
||||
}];
|
||||
|
||||
return entityIdentifierAttributes;
|
||||
}
|
||||
|
||||
// Set Logging Component
|
||||
#undef RKLogComponent
|
||||
@@ -64,69 +150,41 @@ extern NSString * const RKObjectMappingNestingAttributeKeyName;
|
||||
return [mapping.objectClass new];
|
||||
}
|
||||
|
||||
RKEntityMapping *entityMapping = (RKEntityMapping *)mapping;
|
||||
id object = nil;
|
||||
id primaryKeyValue = nil;
|
||||
NSString *primaryKeyAttribute;
|
||||
|
||||
NSEntityDescription *entity = [entityMapping entity];
|
||||
RKAttributeMapping *primaryKeyAttributeMapping = nil;
|
||||
|
||||
primaryKeyAttribute = [entityMapping primaryKeyAttribute];
|
||||
if (primaryKeyAttribute) {
|
||||
// If a primary key has been set on the object mapping, find the attribute mapping
|
||||
// so that we can extract any existing primary key from the mappable data
|
||||
for (RKAttributeMapping *attributeMapping in entityMapping.attributeMappings) {
|
||||
if ([attributeMapping.destinationKeyPath isEqualToString:primaryKeyAttribute]) {
|
||||
primaryKeyAttributeMapping = attributeMapping;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the primary key value out of the mappable data (if any)
|
||||
if ([primaryKeyAttributeMapping.sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) {
|
||||
RKLogDebug(@"Detected use of nested dictionary key as primaryKey attribute...");
|
||||
primaryKeyValue = [[representation allKeys] lastObject];
|
||||
} else {
|
||||
NSString* keyPathForPrimaryKeyElement = primaryKeyAttributeMapping.sourceKeyPath;
|
||||
if (keyPathForPrimaryKeyElement) {
|
||||
primaryKeyValue = [representation valueForKeyPath:keyPathForPrimaryKeyElement];
|
||||
} else {
|
||||
RKLogWarning(@"Unable to find source attribute for primaryKeyAttribute '%@': unable to find existing object instances by primary key.", primaryKeyAttribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RKEntityMapping *entityMapping = (RKEntityMapping *)mapping;
|
||||
NSDictionary *entityIdentifierAttributes = RKEntityIdentifierAttributesForEntityMappingWithRepresentation(entityMapping, representation);
|
||||
if (! self.managedObjectCache) {
|
||||
RKLogWarning(@"Performing managed object mapping with a nil managed object cache:\n"
|
||||
"Unable to update existing object instances by primary key. Duplicate objects may be created.");
|
||||
}
|
||||
|
||||
// If we have found the primary key attribute & value, try to find an existing instance to update
|
||||
if (primaryKeyAttribute && primaryKeyValue && NO == [primaryKeyValue isEqual:[NSNull null]]) {
|
||||
object = [self.managedObjectCache findInstanceOfEntity:entity
|
||||
withPrimaryKeyAttribute:primaryKeyAttribute
|
||||
value:primaryKeyValue
|
||||
inManagedObjectContext:self.managedObjectContext];
|
||||
|
||||
if (object && [self.managedObjectCache respondsToSelector:@selector(didFetchObject:)]) {
|
||||
[self.managedObjectCache didFetchObject:object];
|
||||
// If we have found the entity identifier attributes, try to find an existing instance to update
|
||||
NSEntityDescription *entity = [entityMapping entity];
|
||||
NSManagedObject *managedObject = nil;
|
||||
if ([entityIdentifierAttributes count]) {
|
||||
NSArray *objects = [self.managedObjectCache managedObjectsWithEntity:entity
|
||||
attributeValues:entityIdentifierAttributes
|
||||
inManagedObjectContext:self.managedObjectContext];
|
||||
if (entityMapping.entityIdentifier.predicate) objects = [objects filteredArrayUsingPredicate:entityMapping.entityIdentifier.predicate];
|
||||
if ([objects count] > 0) {
|
||||
managedObject = objects[0];
|
||||
if ([objects count] > 1) RKLogWarning(@"Managed object cache returned %d objects for the identifier configured for the '%@' entity, expected 1.", [objects count], [entity name]);
|
||||
}
|
||||
if (managedObject && [self.managedObjectCache respondsToSelector:@selector(didFetchObject:)]) {
|
||||
[self.managedObjectCache didFetchObject:managedObject];
|
||||
}
|
||||
}
|
||||
|
||||
if (object == nil) {
|
||||
object = [[NSManagedObject alloc] initWithEntity:entity
|
||||
if (managedObject == nil) {
|
||||
managedObject = [[NSManagedObject alloc] initWithEntity:entity
|
||||
insertIntoManagedObjectContext:self.managedObjectContext];
|
||||
if (primaryKeyAttribute && primaryKeyValue && ![primaryKeyValue isEqual:[NSNull null]]) {
|
||||
[object setValue:primaryKeyValue forKey:primaryKeyAttribute];
|
||||
}
|
||||
[managedObject setValuesForKeysWithDictionary:entityIdentifierAttributes];
|
||||
|
||||
if ([self.managedObjectCache respondsToSelector:@selector(didCreateObject:)]) {
|
||||
[self.managedObjectCache didCreateObject:object];
|
||||
[self.managedObjectCache didCreateObject:managedObject];
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
return managedObject;
|
||||
}
|
||||
|
||||
// Mapping operations should be executed against managed object contexts with the `NSPrivateQueueConcurrencyType` concurrency type
|
||||
@@ -150,27 +208,25 @@ extern NSString * const RKObjectMappingNestingAttributeKeyName;
|
||||
if ([mappingOperation.objectMapping isKindOfClass:[RKEntityMapping class]]) {
|
||||
[self emitDeadlockWarningIfNecessary];
|
||||
|
||||
NSArray *connectionMappings = [(RKEntityMapping *)mappingOperation.objectMapping connectionMappings];
|
||||
if ([connectionMappings count] > 0 && self.managedObjectCache == nil) {
|
||||
NSArray *connections = [(RKEntityMapping *)mappingOperation.objectMapping connections];
|
||||
if ([connections count] > 0 && self.managedObjectCache == nil) {
|
||||
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Cannot map an entity mapping that contains connection mappings with a data source whose managed object cache is nil." };
|
||||
NSError *localError = [NSError errorWithDomain:RKErrorDomain code:RKMappingErrorNilManagedObjectCache userInfo:userInfo];
|
||||
if (error) *error = localError;
|
||||
return NO;
|
||||
}
|
||||
|
||||
for (RKConnectionMapping *connectionMapping in connectionMappings) {
|
||||
RKRelationshipConnectionOperation *operation = [[RKRelationshipConnectionOperation alloc] initWithManagedObject:mappingOperation.destinationObject
|
||||
connectionMapping:connectionMapping
|
||||
managedObjectCache:self.managedObjectCache];
|
||||
__weak RKRelationshipConnectionOperation *weakOperation = operation;
|
||||
for (RKConnectionDescription *connection in connections) {
|
||||
RKConnectionOperation *operation = [[RKConnectionOperation alloc] initWithManagedObject:mappingOperation.destinationObject connection:connection managedObjectCache:self.managedObjectCache];
|
||||
__weak RKConnectionOperation *weakOperation = operation;
|
||||
[operation setCompletionBlock:^{
|
||||
if (weakOperation.connectedValue) {
|
||||
if ([mappingOperation.delegate respondsToSelector:@selector(mappingOperation:didConnectRelationship:withValue:usingMapping:)]) {
|
||||
[mappingOperation.delegate mappingOperation:mappingOperation didConnectRelationship:connectionMapping.relationship withValue:weakOperation.connectedValue usingMapping:connectionMapping];
|
||||
[mappingOperation.delegate mappingOperation:mappingOperation didConnectRelationship:connection.relationship toValue:weakOperation.connectedValue usingConnection:connection];
|
||||
}
|
||||
} else {
|
||||
if ([mappingOperation.delegate respondsToSelector:@selector(mappingOperation:didFailToConnectRelationship:usingMapping:)]) {
|
||||
[mappingOperation.delegate mappingOperation:mappingOperation didFailToConnectRelationship:connectionMapping.relationship usingMapping:connectionMapping];
|
||||
[mappingOperation.delegate mappingOperation:mappingOperation didFailToConnectRelationship:connection.relationship usingConnection:connection];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
Reference in New Issue
Block a user