Breakthrough on parent/child MOC setup

This commit is contained in:
Blake Watters
2012-07-13 14:11:48 -04:00
parent f0706dbdbf
commit cfa0df1841
10 changed files with 45 additions and 326 deletions

View File

@@ -1,18 +0,0 @@
//
// RKEntityConnectionOperation.h
// RestKit
//
// Created by Blake Watters on 7/4/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import <Foundation/Foundation.h>
@class NSManagedObject, RKEntityMapping;
@protocol RKManagedObjectCaching;
@interface RKEntityConnectionOperation : NSOperation
- (id)initWithManagedObject:(NSManagedObject *)managedObject entityMapping:(RKEntityMapping *)entityMapping managedObjectCache:(id<RKManagedObjectCaching>)managedObjectCache;
@end

View File

@@ -1,213 +0,0 @@
//
// RKEntityConnectionOperation.m
// RestKit
//
// Created by Blake Watters on 7/4/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import "RKEntityConnectionOperation.h"
#import "RKEntityMapping.h"
#import "RKDynamicObjectMappingMatcher.h"
#import "RKManagedObjectStore.h"
#import "RKLog.h"
#import "RKConnectionMapping.h"
#import "RKManagedObjectCaching.h"
#import "RKRelationshipConnectionOperation.h"
@interface RKEntityConnectionOperation ()
@property (nonatomic, retain) NSManagedObjectID *managedObjectID;
@property (nonatomic, retain) NSManagedObjectContext *parentManagedObjectContext;
@property (nonatomic, retain) NSManagedObjectContext *childManagedObjectContext;
@property (nonatomic, retain) NSManagedObject *childManagedObject;
@property (nonatomic, retain) RKEntityMapping *entityMapping;
@property (nonatomic, retain) id<RKManagedObjectCaching> managedObjectCache;
@end
@implementation RKEntityConnectionOperation
@synthesize managedObjectID = _managedObjectID;
@synthesize parentManagedObjectContext = _parentManagedObjectContext;
@synthesize childManagedObjectContext = _childManagedObjectContext;
@synthesize childManagedObject = _childManagedObject;
@synthesize entityMapping = _entityMapping;
@synthesize managedObjectCache = _managedObjectCache;
- (id)initWithManagedObject:(NSManagedObject *)managedObject entityMapping:(RKEntityMapping *)entityMapping managedObjectCache:(id<RKManagedObjectCaching>)managedObjectCache
{
self = [super init];
if (self) {
self.managedObjectID = [managedObject objectID];
self.parentManagedObjectContext = [managedObject managedObjectContext];
self.entityMapping = entityMapping;
}
return self;
}
- (void)dealloc
{
self.managedObjectID = nil;
self.parentManagedObjectContext = nil;
self.childManagedObjectContext = nil;
self.entityMapping = nil;
self.managedObjectCache = nil;
[super dealloc];
}
//- (void)addDependenciesForConnectionMappings
//{
// RKLogTrace(@"Connecting relationships for managed object %@ using connection mappings: %@", self.managedObject, self.entityMapping.connections);
// for (RKConnectionMapping *connectionMapping in self.entityMapping.connections) {
// RKRelationshipConnectionOperation *operation = [[RKRelationshipConnectionOperation alloc] initWithManagedObject:self.managedObject connectionMapping:connectionMapping managedObjectCache:self.managedObjectCache];
// [self addDependency:operation];
// NSLog(@"Adding dependencies to queue: %@", [NSOperationQueue currentQueue]);
// [[NSOperationQueue currentQueue] addOperation:operation];
// [operation release];
// }
//
// NSLog(@"Added dependencies: %@", self.dependencies);
//}
- (NSManagedObject *)findOneConnectedWithSourceValue:(id)sourceValue connectionMapping:(RKConnectionMapping *)connectionMapping
{
return [self.managedObjectCache findInstanceOfEntity:self.entityMapping.entity
withPrimaryKeyAttribute:connectionMapping.destinationKeyPath
value:sourceValue
inManagedObjectContext:self.childManagedObjectContext];
}
- (NSMutableSet *)findAllConnectedWithSourceValue:(id)sourceValue connectionMapping:(RKConnectionMapping *)connectionMapping
{
NSMutableSet *result = [NSMutableSet set];
id values = nil;
if ([sourceValue conformsToProtocol:@protocol(NSFastEnumeration)]) {
values = sourceValue;
} else {
values = [NSArray arrayWithObject:sourceValue];
}
for (id value in values) {
NSArray *objects = [self.managedObjectCache findInstancesOfEntity:self.entityMapping.entity
withPrimaryKeyAttribute:connectionMapping.destinationKeyPath
value:value
inManagedObjectContext:self.childManagedObjectContext];
[result addObjectsFromArray:objects];
}
return result;
}
- (BOOL)isToManyWithConnectionMapping:(RKConnectionMapping *)connectionMapping
{
NSEntityDescription *entity = [self.childManagedObject entity];
NSDictionary *relationships = [entity relationshipsByName];
NSRelationshipDescription *relationship = [relationships objectForKey:connectionMapping.relationshipName];
return relationship.isToMany;
}
- (BOOL)checkMatcherWithConnectionMapping:(RKConnectionMapping *)connectionMapping
{
if (!connectionMapping.matcher) {
return YES;
} else {
return [connectionMapping.matcher isMatchForData:self.childManagedObject];
}
}
- (id)findConnectedWithConnectionMapping:(RKConnectionMapping *)connectionMapping
{
if ([self checkMatcherWithConnectionMapping:connectionMapping]) {
BOOL isToMany = [self isToManyWithConnectionMapping:connectionMapping];
NSLog(@"Attempting to get valueForKey %@ from childManagedObject: %@", connectionMapping.sourceKeyPath, self.childManagedObject);
id sourceValue = [self.childManagedObject valueForKey:connectionMapping.sourceKeyPath];
if (isToMany) {
return [self findAllConnectedWithSourceValue:sourceValue connectionMapping:connectionMapping];
} else {
return [self findOneConnectedWithSourceValue:sourceValue connectionMapping:connectionMapping];
}
} else {
return nil;
}
}
- (void)connectRelationshipWithConnectionMapping:(RKConnectionMapping *)connectionMapping
{
RKLogTrace(@"Connecting relationship '%@'", connectionMapping.relationshipName);
id relatedObject = [self findConnectedWithConnectionMapping:connectionMapping];
if (relatedObject) {
[self.childManagedObject setValue:relatedObject forKeyPath:connectionMapping.relationshipName];
RKLogDebug(@"Connected relationship '%@' to object '%@'", connectionMapping.relationshipName, relatedObject);
} else {
RKEntityMapping *entityMapping = (RKEntityMapping *)connectionMapping.mapping;
RKLogDebug(@"Failed to find instance of '%@' to connect relationship '%@'", [[entityMapping entity] name], connectionMapping.relationshipName);
}
}
- (void)connectRelationships
{
RKLogTrace(@"Connecting relationships for managed object %@ using connection mappings: %@", self.childManagedObject, self.entityMapping.connections);
for (RKConnectionMapping *connectionMapping in self.entityMapping.connections) {
[self connectRelationshipWithConnectionMapping:connectionMapping];
}
}
- (void)performConnections
{
self.childManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// [self.childManagedObjectContext release];
NSLog(@"The parent managed object context = %@. Concurrency Type = %d", self.parentManagedObjectContext, self.parentManagedObjectContext.concurrencyType);
NSLog(@"The child managed object context = %@. Concurrency Type = %d", self.childManagedObjectContext, self.childManagedObjectContext.concurrencyType);
__block BOOL success;
__block NSError *error;
[self.childManagedObjectContext performBlockAndWait:^{
self.childManagedObjectContext.parentContext = self.parentManagedObjectContext;
// Sanity checking...
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"RKHuman"];
[fetchRequest setIncludesSubentities:YES];
// [fetchRequest setIncludesPendingChanges:YES];
NSLog(@"About to execute a fetch...");
// NSArray *objects = [self.parentManagedObjectContext executeFetchRequest:fetchRequest error:&error];
// NSLog(@"The following objects are in the parent store: %@", objects);
NSArray *objects = [self.childManagedObjectContext executeFetchRequest:fetchRequest error:&error];
NSLog(@"The following objects are in the child store: %@", objects);
if (! objects) {
}
// self.childManagedObject = [self.childManagedObjectContext objectWithID:self.managedObjectID];
NSLog(@"What the fuck???");
// NSLog(@"The child object's railsID is: ", [self.childManagedObject valueForKey:@"railsID"]);
[self connectRelationships];
if ([self.childManagedObjectContext hasChanges]) {
RKLogInfo(@"Connection of relationships was successful, saving child managed object context...");
success = [self.childManagedObjectContext save:&error];
if (success) {
success = [self.parentManagedObjectContext save:&error];
if (! success) {
RKLogError(@"Failed to save connection changes to parent managed object context: %@", [error localizedDescription]);
}
} else {
RKLogError(@"Saving of child managed object context failed: %@", [error localizedDescription]);
}
}
}];
}
- (void)main
{
@try {
[self performConnections];
}
@catch (NSException *exception) {
NSLog(@"Got an exception!!!!");
}
}
@end

View File

@@ -149,6 +149,7 @@ BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue);
RKDynamicObjectMappingMatcher* matcher = [[RKDynamicObjectMappingMatcher alloc] initWithKey:keyPath value:value primaryKeyAttribute:sourceKeyPath];
RKConnectionMapping* mapping = [RKConnectionMapping connectionMappingForRelationship:relationshipName fromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath withMapping:objectOrDynamicMapping matcher:matcher];
[self addConnectionMapping:mapping];
[matcher release];
}
- (void)connectRelationship:(NSString *)relationshipName withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping fromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath usingEvaluationBlock:(BOOL (^)(id data))block
@@ -156,6 +157,7 @@ BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue);
RKDynamicObjectMappingMatcher* matcher = [[RKDynamicObjectMappingMatcher alloc] initWithPrimaryKeyAttribute:sourceKeyPath evaluationBlock:block];
RKConnectionMapping* mapping = [RKConnectionMapping connectionMappingForRelationship:relationshipName fromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath withMapping:objectOrDynamicMapping matcher:matcher];
[self addConnectionMapping:mapping];
[matcher release];
}
- (id)defaultValueForMissingAttribute:(NSString *)attributeName

View File

@@ -56,7 +56,6 @@
RKLogError(@"Failed to execute fetch request due to error: %@", error);
}
RKLogDebug(@"Found objects '%@' using fetchRequest '%@'", objects, fetchRequest);
[fetchRequest release];
return objects;
}
@@ -67,7 +66,7 @@
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];

View File

@@ -44,6 +44,16 @@
return self;
}
- (void)dealloc
{
self.managedObjectCache = nil;
self.managedObjectContext = nil;
self.operationQueue = nil;
[super dealloc];
}
// TODO: Encapsulate this logic into another class...
- (id)objectForMappableContent:(id)mappableContent mapping:(RKObjectMapping *)mapping
{
NSAssert(mappableContent, @"Mappable data cannot be nil");
@@ -56,16 +66,16 @@
RKEntityMapping *entityMapping = (RKEntityMapping *)mapping;
id object = nil;
id primaryKeyValue = nil;
NSString* primaryKeyAttribute;
NSString *primaryKeyAttribute;
NSEntityDescription* entity = [entityMapping entity];
RKObjectAttributeMapping* primaryKeyAttributeMapping = nil;
NSEntityDescription *entity = [entityMapping entity];
RKObjectAttributeMapping *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 (RKObjectAttributeMapping* attributeMapping in entityMapping.attributeMappings) {
for (RKObjectAttributeMapping *attributeMapping in entityMapping.attributeMappings) {
if ([attributeMapping.destinationKeyPath isEqualToString:primaryKeyAttribute]) {
primaryKeyAttributeMapping = attributeMapping;
break;
@@ -113,41 +123,8 @@
return object;
}
// TODO: Encapsulate the connection logic into a new object...
// The findConnected: etc methods from the connection mapping should live on the connector/connectionOperation
//- (void)connectRelationshipForManagedObject:(NSManagedObject *)managedObject connectionMapping:(RKConnectionMapping *)connectionMapping
//{
// RKLogTrace(@"Connecting relationship '%@'", connectionMapping.relationshipName);
//
// id relatedObject = [connectionMapping findConnected:managedObject usingCache:self.managedObjectCache];
// if (relatedObject) {
// [managedObject setValue:relatedObject forKeyPath:connectionMapping.relationshipName];
// RKLogDebug(@"Connected relationship '%@' to object '%@'", connectionMapping.relationshipName, relatedObject);
// } else {
// RKEntityMapping *objectMapping = (RKEntityMapping *)connectionMapping.mapping;
// RKLogDebug(@"Failed to find instance of '%@' to connect relationship '%@'", [[objectMapping entity] name], connectionMapping.relationshipName);
// }
//}
//- (void)connectRelationshipsForManagedObject:(NSManagedObject *)managedObject entityMapping:(RKEntityMapping *)entityMapping
//{
// RKLogTrace(@"Connecting relationships for managed object %@ using connection mappings: %@", managedObject, entityMapping.connections);
// for (RKConnectionMapping *connectionMapping in entityMapping.connections) {
// if (self.queue) {
// RKLogTrace(@"Enqueueing relationship connection using operation queue");
// __block RKManagedObjectMappingOperationDataSource *selfRef = self;
// [self.queue addOperationWithBlock:^{
// [selfRef connectRelationshipForManagedObject:managedObject connectionMapping:connectionMapping];
// }];
// } else {
// [self connectRelationshipForManagedObject:managedObject connectionMapping:connectionMapping];
// }
// }
//}
/*
Mapping operations should be executed against managed object contexts
Mapping operations should be executed against managed object contexts with NSPrivateQueueConcurrencyType
*/
- (BOOL)executingConnectionOperationsWouldDeadlock
{
@@ -169,18 +146,10 @@
if ([mappingOperation.objectMapping isKindOfClass:[RKEntityMapping class]]) {
[self emitDeadlockWarningIfNecessary];
NSLog(@"The destination object is: %@. isInserted = %d", mappingOperation.destinationObject, [mappingOperation.destinationObject isInserted]);
// RKEntityConnectionOperation *operation = [[RKEntityConnectionOperation alloc] initWithManagedObject:mappingOperation.destinationObject
// entityMapping:(RKEntityMapping *)mappingOperation.objectMapping
// managedObjectCache:self.managedObjectCache];
for (RKConnectionMapping *connectionMapping in [(RKEntityMapping *)mappingOperation.objectMapping connections]) {
RKEntityConnectionOperation *operation = [[RKEntityConnectionOperation alloc] initWithManagedObject:mappingOperation.destinationObject
entityMapping:(RKEntityMapping *)mappingOperation.objectMapping
managedObjectCache:self.managedObjectCache];
// RKRelationshipConnectionOperation *operation = [[RKRelationshipConnectionOperation alloc] initWithManagedObject:mappingOperation.destinationObject
// connectionMapping:connectionMapping
// managedObjectCache:self.managedObjectCache];
RKRelationshipConnectionOperation *operation = [[RKRelationshipConnectionOperation alloc] initWithManagedObject:mappingOperation.destinationObject
connectionMapping:connectionMapping
managedObjectCache:self.managedObjectCache];
if (self.operationQueue) {
[self.operationQueue addOperation:operation];
} else {
@@ -188,8 +157,6 @@
}
[operation release];
}
// [self connectRelationshipsForManagedObject:mappingOperation.destinationObject entityMapping:(RKEntityMapping *)mappingOperation.objectMapping];
}
}

View File

@@ -299,7 +299,7 @@ static RKManagedObjectStore *defaultObjectStore = nil;
- (NSManagedObjectContext *)managedObjectContextWithConcurrencyType:(NSManagedObjectContextConcurrencyType)concurrencyType
{
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:concurrencyType];
NSManagedObjectContext *managedObjectContext = [[[NSManagedObjectContext alloc] initWithConcurrencyType:concurrencyType] autorelease];
[managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
[managedObjectContext setUndoManager:nil];
[managedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];

View File

@@ -47,7 +47,7 @@
self.managedObject = nil;
self.connectionMapping = nil;
self.managedObjectCache = nil;
[super dealloc];
}
@@ -136,8 +136,6 @@
RKLogDebug(@"Failed to find instance of '%@' to connect relationship '%@'", [[objectMapping entity] name], self.connectionMapping.relationshipName);
}
}];
NSLog(@"Huh???");
}
- (void)main

View File

@@ -32,6 +32,16 @@
@implementation RKManagedObjectMappingOperationTest
- (void)setUp
{
[RKTestFactory setUp];
}
- (void)tearDown
{
[RKTestFactory tearDown];
}
- (void)testMappingInPrivateQueue
{
RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
@@ -470,13 +480,9 @@
- (void)testShouldConnectRelationshipsByPrimaryKeyRegardlessOfOrder
{
// RKLogConfigureByName("*", RKLogLevelTrace);
RKManagedObjectStore *store = [RKTestFactory managedObjectStore];
RKLogConfigureByName("*", RKLogLevelTrace);
// NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// managedObjectContext.parentContext = store.primaryManagedObjectContext;
// [managedObjectContext setUndoManager:nil];
// [managedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
RKManagedObjectStore *store = [RKTestFactory managedObjectStore];
NSManagedObjectContext *managedObjectContext = store.primaryManagedObjectContext;
RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityWithName:@"RKParent" inManagedObjectContext:managedObjectContext];
@@ -498,53 +504,29 @@
RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectContext
cache:managedObjectCache];
NSOperationQueue *operationQueue = [NSOperationQueue new];
[operationQueue setMaxConcurrentOperationCount:1];
[operationQueue setSuspended:YES];
mappingOperationDataSource.operationQueue = operationQueue;
__block RKObjectMappingResult *result;
__block NSError *error;
__block BOOL success;
[managedObjectContext performBlockAndWait:^{
RKObjectMapper *mapper = [RKObjectMapper mapperWithObject:JSON mappingProvider:mappingProvider];
mapper.mappingOperationDataSource = mappingOperationDataSource;
result = [mapper performMapping];
// Sanity checking...
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"RKHuman"];
[fetchRequest setIncludesSubentities:YES];
// [fetchRequest setIncludesPendingChanges:YES];
NSArray *objects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (! objects) {
NSLog(@"The following objects are in the store: %@", objects);
}
[mapper performMapping];
}];
// Now run the queue to completion...
// [managedObjectContext performBlockAndWait:^{
NSLog(@"SHOULD NOT HIT THE OPERATIONS BEFORE THIS");
NSLog(@"The following operations are in queue: %@", [operationQueue operations]);
[operationQueue setSuspended:NO];
// [operationQueue waitUntilAllOperationsAreFinished];
// success = [managedObjectContext save:&error];
// if (! success) {
// NSLog(@"Failed to save managed object context: %@", error);
// }
// }];
[operationQueue setSuspended:NO];
[operationQueue waitUntilAllOperationsAreFinished];
[managedObjectContext performBlockAndWait:^{
NSError *error;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"RKParent"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"parentID = %@", @1];
fetchRequest.fetchLimit = 1;
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
RKParent *parent = [results lastObject];
assertThat(parent, is(notNilValue()));
// NSArray *children = [[result asDictionary] valueForKey:@"children"];
// assertThat(children, hasCountOf(1));
// RKChild *child = [children lastObject];
// assertThat(child.father, is(notNilValue()));
NSSet *children = [parent fatheredChildren];
assertThat(children, hasCountOf(1));
RKChild *child = [children anyObject];
assertThat(child.father, is(notNilValue()));
}];
}

View File

@@ -27,6 +27,7 @@
@interface RKParent : RKHuman {
}
@property (nonatomic, retain) NSSet *fatheredChildren;
@property (nonatomic, retain) NSSet *children;
@property (nonatomic, retain) NSNumber *parentID;

View File

@@ -24,6 +24,7 @@
@implementation RKParent
@dynamic fatheredChildren;
@dynamic children;
@dynamic parentID;