RKMappingOperation doesn't connect relationships - ios

I'm trying to perform object mapping manually by using RKMappingOperation, but it seems like it's not connecting the relationships which are set in RKEntityMapping.
I've created a demo project, you can check out here: https://github.com/HiveHicks/RKMappingOperationTest
There are two entities in the model: Employee and Department with a many-to-one relationship called (department can have many employees, and one employee can work only in one department). There is also a class called TestOperation, which performs mapping in background using RKMappingOperation. As you can see in -[TestOperation mappingForObject:], I set up a connection for employee mapping like this:
[mapping addConnectionForRelationship:EmployeeRelationships.department connectedBy:#{
EmployeeAttributes.departmentGuid : DepartmentAttributes.guid
}];
However, when the operation finishes, the mapped managed objects have all the attributes, but no relationship
How can I fix this?
UPDATE
It turns out that RKMapperOperation maps attributes for managed objects synchronously, but creates asynchronous operation for connecting relationships. That leads to the following situation:
When -[RKMapperOperation execute:] returns, I save the context. All the attributes are there, but relationship is not, because RKRelationshipConnectionOperation is added to the queue, on which I am right now.
RKRelationshipConnectionOperation only sets the relationship, it doesn't save the context.
I'm left with a dirty context and no idea about when to save it, because RKMapperOperationDelegate's mapperDidFinishMapping: method is called before RKRelationshipConnectionOperation starts.
The picture below proves what I just said
So, the solution that I see right now is to create new operation queue in SyncOperation, add RKMapperOperation to it, and then wait until all the operations in this internal queue finish executing. WOW. Sounds like a hack. I'll give it a try right now.
UPDATE 2
It works with the solution I described. Solved. I've commited the final version.

The solution is to create temporary operation queue, add RKMapperOperation to it and call waitUntilAllOperationsAreFinished on this queue. After that, your context is guaranteed to have all the needed relationships set.
The working project is posted on github: https://github.com/HiveHicks/RKMappingOperationTest
id<RKManagedObjectCaching> cache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:_context];
RKManagedObjectMappingOperationDataSource *dataSource =
[[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:_context cache:cache];
RKMapperOperation *mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:objects mappingsDictionary:#{
[NSNull null] : [self mapping]
}];
mapperOperation.mappingOperationDataSource = dataSource;
mapperOperation.delegate = self;
NSOperationQueue *operationQueue = [NSOperationQueue new];
operationQueue.name = #"Internal SyncOperation queue";
[operationQueue addOperation:mapperOperation];
[operationQueue waitUntilAllOperationsAreFinished];
[_context performBlockAndWait:^{
NSError *saveError = nil;
if (![_context saveToPersistentStore:&saveError]) {
NSLog(#"Error saving to store: %#", saveError);
}
}];

Related

RestKit - mixing entity mapping and object mapping

I am using RestKit in my iOS app and up until now I was simply using object mapping because I wasn't persisting any data to CoreData. I now want to add the possibility for the users to download some data, and use that data if the user has no internet connection.
I understand I have to use Entity Mapping for that, but I'm running into an issue: how can I use two different mappings for the same request? I mean, I don't understand how I am supposed to handle these two cases. Whether the users decided to download the data or is just requesting it to display once, the URL path will be exactly the same. How can I tell RestKit once to store it in CoreData and the other time to simply map it with ObjectMapping?
Basically, I'm asking the same question as this one: How to use Core Data models without saving them?
but specifically for RestKit instead of MagicalRecords.
I just had a similar issue: I needed to obtain a token that was being returned in addition to a user object that I am mapping to Core Data. The token is sent on it's own in the JSON response so I wasn't sure how to extract it.
In the end I used the following:
[operation setWillMapDeserializedResponseBlock:^id(id deserializedResponseBody) {
NSDictionary *dictionary = [[NSMutableDictionary alloc] init];
dictionary = deserializedResponseBody;
self.token = [dictionary objectForKey:#"token"];
return deserializedResponseBody;
}];
The JSON is in the format:
{
“token”: “....”,
“user”: {
....
}
}
The operation setWillMapDeserializedResponseBlock method gives you the chance to manipulate the results before mapping takes place - or to grab other data that's not covered by your object mapping. Works nicely.
The right way to handle this case is to use different ManagedObjectContexts.
You will need one for the persistent data, and it can be set up like this:
// Initialize managed object store
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
objectManager.managedObjectStore = managedObjectStore;
[managedObjectStore createPersistentStoreCoordinator];
[[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
Then, you can create a second context, that will be temporary only:
NSManagedObjectContext *newTemporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; // Choose your concurrency type, or leave it off entirely
[newTemporaryContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
newTemporaryContext.persistentStoreCoordinator = coordinator;
Finally, once this is done, you should store a reference to your temporary context somewhere, and decide on which context to used, based on the context of your app.

RestKit merging context function is nil'ing properties

So I create an object
MyObject *object = [mainManagedObjectContext insertNewObjectForEntityForName:#"MyObject"];
SomeOtherObject *someOtherObject = [s_mainManagedObjectContext insertNewObjectForEntityForName:#"MyObject"];
// in case you're wondering the inverse relationship of someOtherObject's relation ship to myObject is one to many
// so this should remove the idea that setting someOtherObjects.relationship to something else was causing the property to nil
object.oneToOneRelationShip = someOtherObject;
// simply used for debuggin
[object addObserver:self forKeyPath:#"oneToOneRelationShip" options:NSKeyValueObservingOptionInitial context:nil];
I then post the object using the object manager to post this thing
NSMutableURLRequest *request = [objectManager requestWithObject:object method:method path:path parameters:parameters];
[objectManager managedObjectRequestOperationWithRequest:request managedObjectContext:mainManagedObjectContext success:^{
NSLog(#"blah");
}];
Now after this occurs the RestKit starts running operation objects which eventually calls save which works it way to this calls
-(void)handleManagedObjectContextDidSaveNotification:(NSNotification *)notification {
...
[self.mergeContext mergeChangesFromContextDidSaveNotification:notification];
...
This is where my KVO observer code signals to me there's a change that was made
object.oneToOneRelationShip == nil
I believed I fixed my problem by simply saving to the persistent store before I post the object, but can someone please explain to me why this occurred. Am I doing something wrong? This bug was very confusing to me. I create objects off the main context so I don't know how this problem could occur.

Core Data stack on serial queue results in deadlock

I'm trying to migrate a core data database to Realm (somewhere between 0-2 million rows), and am running into a deadlock that as far as I can tell, shouldn't be happening.
From a Singleton class, I'm launching the migration like this:
_queue = dispatch_queue_create("DiagnosticMigrationQueue", NULL);
dispatch_async(_queue, ^{
_realmMigrator = [[CoreDataToRealmMigrator alloc] init];
[_realmMigrator performMigrationToRealm];
});
Within the performMigrationToRealm method, I set up the Core Data stack thusly:
- (void) performMigrationToRealm
{
self.migrationIsRunning = YES;
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"persistentStore"];
NSError *error;
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
context.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
[context.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"DiagnosticData"
URL:url
options:nil
error:&error];
Understanding Check: When I create the queue, none of the Core Data stack has been set up yet. Therefore, the NSManagedObjectContext is only created on whatever thread GCD decides to put my block on.
So far, so good. No problems. I now run a method that - in batches of 100,000 - grabs all the NSManagedObjectIds in the Entity. It looks something like this:
for (NSInteger numberOfMigratedBatches = 0; numberOfMigratedBatches < totalNumberOfBatches; numberOfMigratedBatches++)
{
NSArray *samples = [context executeFetchRequest:fetchRequest error:&error];
if (samples && !error)
{
[self transferWeightSamplesToRealmWithObjectIds:samples withContext:context];
}
[context reset];
fetchRequest.fetchOffset = batchSize * (numberOfMigratedBatches+1);
}
The line that goes funky is the fetchRequest in the code block above. Even though I've launched this process on a serial queue, I somehow end up with this:
Each of those minion_duties2 threads is stuck on the same line of code, namely the fetchRequest above.
What is going on here? I understand that queues != threads, and that GDC will put my code on whichever thread is sees fit. However, I wouldn't expect that it would put my code on THREE threads. Also, what is com.apple.root.user-initiated-qos.overcommit? I'll say it's overcommitted. I only want this code run one time!
So, this is technically not an answer to the question you posted, but, it could potentially provide you with a way that would allow you to avoid the problem you're running into (workaround?).
Since my guess is that it's the several different fetch requests trying to execute on several different threads that's causing the deadlock, I would suggest that instead of doing the batching manually, you do it by setting the fetchBatchSize property on NSFetchRequest, and let core data do it for you, and hence avoid doing multiple fetch requests altogether, if memory usage is an issue, try wrapping the innards of your for loop in an autoreleasepool block (I'm guessing there is a for loop inside the transferWeightSamplesToRealmWithObjectIds:withContext: method).
Core data has a concept called batching & faulting. From Apple documentation of NSFetchRequest:
If you set a non-zero batch size, the collection of objects returned
when the fetch is executed is broken into batches. When the fetch is
executed, the entire request is evaluated and the identities of all
matching objects recorded, but no more than batchSize objects’ data
will be fetched from the persistent store at a time. The array
returned from executing the request will be a proxy object that
transparently faults batches on demand. (In database terms, this is an
in-memory cursor.)
Hopefully, this helps you somewhat.

Restore NSManagedObject relationships in background threads

I'm developing an application through Core Data and I need to perform some calculation in a background thread to create an xml file based on specific NSManagedObject.
Following the documentation, I set up NSOperation subclass. This class has a property like the following:
#property (nonatomic, retain) NSArray* objectIDs;
where objectIDs is an array of managed object ids (of type NSManagedObjectID). This is necessary according to the documentation: NSManagedObject are not thread safe.
Inside the main of NSOperation subclass I'm doing the following:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSManagedObjectContext *exportContext = [[NSManagedObjectContext alloc] init];
[exportContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
for (NSManagedObjectID* objectID in self.orderObjectIDs) {
NSError* error = nil;
Order* order = (Order*)[exportContext existingObjectWithID:objectID error:&error];
// order.someRelationship is fault here...
// Create XML file here...
}
[exportContext reset];
[exportContext release], exportContext = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:kRegisterComplete object:self];
[pool drain];
pool = nil;
Inside the for loop I'm fetching the right object using existingObjectWithID:error method of NSManagedObjectContext class since
Unlike objectWithID:, this method never returns a fault.
The method works. I'm able to retrieve the properties of that retrieved object. The only problem is that relationships are fetched as faults.
Said this, I have two questions.
First, is this the right approach to fecth NSManagedObject in a background thread?
Then, how can I fetch relationships for each fetched object within the for loop? Do I have to create a NSFetchedRequest to fetch the relationship object based on the specific object that has been fetched through the id?
Thank you in advance.
Why do you care that the relationship is a fault? Accessing it will fill the fault and return the data, are you concerned about disk I/O for some reason?
You may try using prefetching to alleviate some of the I/O overhead, but it's utility is limited to the relationship on the entity being fetched:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html
In that case you would create a fetch request with a predicate like so:
[NSPredicate predicateWithFormat:#"SELF == %#", objectID];
That will return to you only the managed object you desire, and with relationship pre-fetching the relationship won't be a fault.
You could further optimize this by using a IN clause and fetching all the objects at once:
[NSPredicate predicateWithFormat:#"SELF IN (%#)", desiredObjectIDs];
You need to make sure of the following:
Before you pass around any object IDs you do - [NSManagedObjectContext obtainPermanentIDsForObjects:error:] on the originating thread.
(on main thread)
[managedObjectContext obtainPermanentIDsForObjects:objectIDs error:nil]
Before attempting to access these objects from another thread (including their relationships) you need to save the NSManagedObjectContext of the originating thread. Otherwise the objects will not exist in persistence and your background thread will be unable to fetch it (or other relationships). Is easiest way to insure this is to call a blocking save on the main thread at the beginning of your NSOperation execution. For example:
dispatch_sync(dispatch_get_main_queue(), {
[managedObjectContext save:nil];
});
You may make an NSFetchRequest or access the relationships directly depending on which way is more convenient; probably accessing the relationships directly. You do not need to call a [NSManagedObjectContext reset] at the end of your routine.

Core Data: nil managed object context on my managed object

I'm using 2 managed object contexts for efficiently important a large data set in the background. I'm ensuring I'm only using 1 managed object context at a time in the thread.
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
MetricType *metricType = [self metricTypeForName:self.metricName fromContext:context];
NSLog(#"metric context %#", [metricType managedObjectContext]);
[self processDataInContext:context forMetric:metricType];
In the snipped of code above, the NSLog correctly prints out the address of the managedObjectContext i'm using. I then go on to processDataInContext - which is just a private method to interate over a json data array and add objects. Each object has a relationship to the MetricType.
However, when I go to associate them
metric.metricType = metricType;
I get the error: Illegal attempt to establish a relationship 'metricType' between objects in different contexts.... even though I'm ensuring I don't do this.
When I do a log output before this line:
NSLog(#"My Context %#", context);
NSLog(#"metric context %#", [metricType managedObjectContext]);
The metricType context returns nil!!
How has it become nilled? I didn't nil it and this seems to be the reason its complaining.
I figured it out
[context reset];
..was being called every 50 records, which of course was removing my metricType object from the context and I wasn't re-fetching it.
The way you initialize metricType looks fishy. What does [self metricTypeForName:fromContext:] actually do to return a MetricType instance? Are you creating a new MetricType using [[MetricType alloc]init]? I suspect you are returning something in an auto-release pool

Resources