Core Data delete NSManagedObject in Context take time? - core-data

This is my first question on StackOverflow. Pardon me if I make some mistakes.
I'm trying to delete all NSManagedObject ESAnswer in a Submission object.
NSLog(#"BEFORE");
for (ESAnswer *ans in _currentSubmission.answer) {
NSLog(#"%#", ans.questionID);
}
// Delete all answer with index from _nextQuestionsIndexStack.m_array
for (NSNumber *index in _nextQuestionsIndexStack.m_array) {
// Find ESAnswer object with questionID
NSString *questionID = _question[index];
NSPredicate *predicateAnswer = [NSPredicate predicateWithFormat:#"questionID LIKE %#", questionID];
ESAnswer *foundAnswer = [[_currentSubmission.answer filteredSetUsingPredicate:predicateAnswer] anyObject];
// Delete object
[context deleteObject:foundAnswer];
NSLog(#"Deleted %#", foundAnswer.questionID);
}
NSLog(#"AFTER");
for (ESAnswer *ans in _currentSubmission.answer) {
NSLog(#"%#", ans.questionID);
}
But the console output was like nothing was deleted (all objects are in the same NSManagedObjectContext):
2013-09-18 11:01:36.541[3661:707] BEFORE
2013-09-18 11:01:36.543[3661:707] 52368675231dbba60e000065
2013-09-18 11:01:36.544[3661:707] 522ff38a93ebc0ce4f000008
2013-09-18 11:01:36.545[3661:707] 52368662231dbba60e000060
2013-09-18 11:01:36.553[3661:707] Deleted 52368675231dbba60e000065
2013-09-18 11:01:36.558[3661:707] Deleted 52368662231dbba60e000060
2013-09-18 11:01:36.563[3661:707] Deleted 522ff38a93ebc0ce4f000008
2013-09-18 11:01:36.564[3661:707] AFTER
2013-09-18 11:01:36.565[3661:707] 52368675231dbba60e000065
2013-09-18 11:01:36.567[3661:707] 522ff38a93ebc0ce4f000008
2013-09-18 11:01:36.570[3661:707] 52368662231dbba60e000060
These answers should be deleted before I calculate the next step.
However in runtime, the calculate method is called with the old data before deleting. When I let it stay for a while, the calculate method is called with the new data.
So, my question is:
1. Is that deleting NSManagedObject in a context take some time to complete?
2. If that so, how can I make the calculate method wait for deleting to complete?
Thank you in advance!

deleteObject: only marks the object for deletion. You can save the context, or call
processPendingChanges to make the context actually perform the changes to the object graph.
processPendingChanges is called automatically during the main event loop, which explains
why you are seeing the changes "later".

Related

Serializing NSManagedObject

I'm using a category on NSManagedObject, called NSManagedObject+Serialization.h located here https://gist.github.com/nuthatch/5607405.
Everything pretty much works great, but I need to implement this but not sure how? I want to skip some objects.
- (NSDictionary*) toDictionary {
// Check to see there are any objects that should be skipped in the traversal.
// This method can be optionally implemented by NSManagedObject subclasses.
NSMutableSet *traversedObjects = nil;
if ([self respondsToSelector:#selector(serializationObjectsToSkip)]) {
traversedObjects = [self performSelector:#selector(serializationObjectsToSkip)];
}
return [self toDictionaryWithTraversalHistory:traversedObjects];
}
How do I add object relationships to be skipped?
Many Thanks
In your managed object subclass you must implement the serializationObjectsToSkip:
- (NSMutableSet*) serializationObjectsToSkip
{
NSMutableSet* objectsToSkip = [NSMutableSet new];
//Here you select objects that relate to this object and you don't want to serialise.
//Insert them into `objectsToSkip`
return objectsToSkip;
}
However, the implementation of the serialisation looks buggy (lines 80 and 93) ... (if you don't supply all objects to skip in advance)
The toDictionary of the relatedObject is skipped and so objects that the related objects may want to skip will not be added to the traversal history set ...
A quick fix might be to replace these lines with the full implementation of toDictionary and merging the traversal history set and the objectsToSkip sets returned ...
A better solution will be to change the signature of the toDictionary method to accept the traversal history and do the set merging there and replace the above lines with toDictionary of the related object.

Core Data NSFetchRequest Sort by Category Method Return Value

How do I sort my fetched results by a value that is returned by a method in a category of the entity I'm fetching?
In my category, I sum up several values from the entity's to-many relationship, then divide by the number of objects in the relationship, effectively creating an average that I return in my category method as a float value.
Here is my code:
In the Category.h
- (float)smallPenaltyAvg;
In the Category.m
- (float)smallPenaltyAvg{
float smallPenaltyAvg = 0;
for (Match *mtch in self.matches) {
smallPenaltyAvg += [mtch.penaltySmall floatValue];
}
if ([self.matches count] > 0) {
smallPenaltyAvg = (float)smallPenaltyAvg/(float)[self.matches count];
}
return smallPenaltyAvg;
}
And when I call it in the Core Data Table View Controller class that I created...
NSFetchRequest *poolRequest = [[NSFetchRequest alloc] initWithEntityName:#"Team"];
poolRequest.predicate = [NSPredicate predicateWithFormat:#"regionalIn.name = %#", _regionalToDisplay];
poolRequest.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"smallPenaltyAvg" ascending:YES]];
And I have the Category.h file imported on every file previously mentioned outside of the Category.h file itself.
It gives me the error of:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath smallPenaltyAvg not found in entity <NSSQLEntity Team id=5>
Am I not allowed to do this?
If I am, what am I doing wrong?
I do not think this has anything to do with the kind of persistent store.
The trick is to create an appropriate attribute in the managed object model, and mark it as Transient. Then override the getter of this attribute to do your calculations.
Now your fetch request should work as expected (although there are some caveats with fetched results controllers).
As for the SQLite problem, when you add the SQLite store with
- (NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType
configuration:(NSString *)configuration
URL:(NSURL *)storeURL
options:(NSDictionary *)options
error:(NSError **)error
just pass NSSQLiteStoreType as the storeType. The other options are binary and in-memory, so in this sense this is indeed the "default".
This is not possible when using a backing SQLite store.
My suggestion is you persist the average property, and maintain it yourself by overriding the Match setCategory: property and making the calculation there for every match added.
What I did to solve my problem was create a new attribute for every average or sum that I needed in the Team object from all of its Match objects' attributes and then created a method in the TeamCategory file that populated those averages and that method was called every time a Match object was inserted into the Team object. It took a while to do, but it works now. If there is a better solution, I'm still open for suggestions.

iOS - Different MOCs and managed objects status

in the context of some tests I'm writing I've found the following issue. I make use of RestKit 0.20.2, among the other natural purposes, also to manage my coredata-related aspects.
In one of those tests, I create a CoreData entity A, containing a Date as one of its fields, by assigning it a Nil value (it's an optional field). My saving function performs the following code:
NSError* myError;
[[RKTestFactory managedObjectStore].mainQueueManagedObjectContext saveToPersistentStore:&myError];
NSLog(#"Save done on main queue with myError: %#", myError);
NSAssert1(myError == Nil, #"Coredata saving error: %#", myError);
After this save, an algorithm is run which operates on A and update that date field. This algorithm works in a private managed object context:
// Get a Local Managed Object Context
NSManagedObjectContext* myLocalMOC = [[DDRestKitManager sharedInstance].objectManager.managedObjectStore newChildManagedObjectContextWithConcurrencyType:NSPrivateQueueConcurrencyType trackChanges:YES];
(DDRestKitManager is a singleton managing about every RestKit-related aspect of my project). This algorithm first retrieves A with a FetchRequest within its private managed object context, then operates on it and finally updates its date field. It then saves every CoreData related aspect it dealt with (including the updated A-status) with a save on its private MOC.
When, in the previous test body, I need to invoke the very same algorithm again on the very same entity A after having updated some of its fields in order to test the new algorithm outcome, I need to put A's date field back to Nil before invoking the algorithm. This is what I do:
A.date_field = Nil;
[[TestCoreDataManager sharedInstance] save];
// <invoke_algorithm>
(TestCoreDataManager is a further singleton providing objects and saving them by means of the previously reported function). The problem is that when the algorithm retrieves again the object, the date_field is not Nil but still contains the previously assigned value.
It seems like the instance of A retrieved by the algorithm in its private context is not up-to-date wrt the underlying persistent store. Is there anyone who might tell me what I'm doing wrong?
Some more details
I've just moved the default semantics of this date field in order to be always not nil and to have 0 as default value. I rewrote the algorithm in order for the test condition to be [A.date_field timeIntervalSince1970] == 0 in place of A.date_field == Nil.
In my code, in order for that test to be met, I use the following code
A.date_field = [NSDate dateWithTimeIntervalSince1970:0];
[TestCoreDataManager save]; // [mainManagedObjectContext saveOnPersistentStore:&myError];
// Get a Local Managed Object Context
NSManagedObjectContext* myLocalMOC =
[[DDRestKitManager sharedInstance].objectManager.managedObjectStore
newChildManagedObjectContextWithConcurrencyType:NSPrivateQueueConcurrencyType
tracksChanges:YES];
<query_for_A_entities_in_the_store>;
At this point A.date_field contains the value it had before resetting to 0 seconds from 1970.
The even more strange part follows. If I replace
A.date_field = [NSDate dateWithTimeIntervalSince1970:0];
with
A.date_field = [NSDate dateWithTimeIntervalSince1970:1];
or
A.date_field = [NSDate dateWithTimeIntervalSince1970:-1];
and leave the rest of the code untouched, then the child managed object context fetch an A object with the date_field now up-to-date to the correct date and time (1970, 1:00:01 a.m. or 0:59:59 a.m. respectively).
This is driving me crazy.

Massive Mutithreading Operations

EDITED WITH NEW CODE BELOW
I'm relatively newbie on Multithreading but to achieve my goal, doing it quickly and learning something new, I decided to do it using a multithread App.
The goal: Parse a huge amount of string from a file and save every word into the SQLite db using CoreData.
Huge because the amount of words is around 300.000 ...
So this is my approach.
Step 1. Parse all the words into the file placing it into a huge NSArray. (Done quickly)
Step 2. Create the NSOperationQueue inserting the NSBlockOperation.
The main problem is that the process start very quickly but than slow down very soon. I'm Using an NSOperationQueue with max concurrent operation setted to 100. I have a Core 2 Duo Process (Dual core without HT).
I seen that using NSOperationQueue there is a lot of overhead creating the NSOperation (stopping the dispatch of the queue it need about 3 min just to create 300k NSOperation.)
CPU goes to 170% when I start dispatching the queue.
I tryed also removing the NSOperationQueue and using the GDC (the 300k loop is done instantaneous (commented lines)) but cpu used is only 95% and the problem is the same as with NSOperations. Very soon the process slow down.
Some tips to do it well?
Here some Code (Original question Code):
- (void)inserdWords:(NSArray *)words insideDictionary:(Dictionary *)dictionary {
NSDate *creationDate = [NSDate date];
__block NSUInteger counter = 0;
NSArray *dictionaryWords = [dictionary.words allObjects];
NSMutableSet *coreDataWords = [NSMutableSet setWithCapacity:words.count];
NSLog(#"Begin Adding Operations");
for (NSString *aWord in words) {
void(^wordParsingBlock)(void) = ^(void) {
#synchronized(dictionary) {
NSManagedObjectContext *context = [(PRDGAppDelegate*)[[NSApplication sharedApplication] delegate] managedObjectContext];
[context lock];
Word *toSaveWord = [NSEntityDescription insertNewObjectForEntityForName:#"Word" inManagedObjectContext:context];
[toSaveWord setCreated:creationDate];
[toSaveWord setText:aWord];
[toSaveWord addDictionariesObject:dictionary];
[coreDataWords addObject:toSaveWord];
[dictionary addWordsObject:toSaveWord];
[context unlock];
counter++;
[self.countLabel performSelectorOnMainThread:#selector(setStringValue:) withObject:[NSString stringWithFormat:#"%lu/%lu", counter, words.count] waitUntilDone:NO];
}
};
[_operationsQueue addOperationWithBlock:wordParsingBlock];
// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// dispatch_async(queue, wordParsingBlock);
}
NSLog(#"Operations Added");
}
Thank you in advance.
Edit...
Thanks to Stephen Darlington I rewrite my code and I figured out the problem. The most important thing is: Do not share CoreData object between Thread ... it means do not mix Core data objects retrieved by different context.
This bring me to use #synchronized(dictionary) that result in a slow motion code execution!
Than I removed the massive NSOperation creation using just MAXTHREAD instance. (2 or 4 instead of 300k ... is a huge difference)
Now I can parse 300k+ String in just 30/40 seconds. Impressive!!
Still I have some issue (seams it parse more words than they are with just 1 thread and it parse not all the words if threads are more than 1 ... I need to figure it out) but now the code is really efficient. Maybe the next step could be using OpenCL and injecting it into the GPU :)
Here the new Code
- (void)insertWords:(NSArray *)words forLanguage:(NSString *)language {
NSDate *creationDate = [NSDate date];
NSPersistentStoreCoordinator *coordinator = [(PRDGAppDelegate*)[[NSApplication sharedApplication] delegate] persistentStoreCoordinator];
// The number of words to be parsed by the single thread.
NSUInteger wordsPerThread = (NSUInteger)ceil((double)words.count / (double)MAXTHREADS);
NSLog(#"Start Adding Operations");
// Here I minimized the number of threads. Every thread will parse and convert a finite number of words instead of 1 word per thread.
for (NSUInteger threadIdx = 0; threadIdx < MAXTHREADS; threadIdx++) {
// The NSBlockOperation.
void(^threadBlock)(void) = ^(void) {
// A new Context for the current thread.
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:coordinator];
// Dictionary now is in accordance with the thread context.
Dictionary *dictionary = [PRDGMainController dictionaryForLanguage:language usingContext:context];
// Stat Variable. Needed to update the UI.
NSTimeInterval beginInterval = [[NSDate date] timeIntervalSince1970];
NSUInteger operationPerInterval = 0;
// The NSOperation Core. It create a CoreDataWord.
for (NSUInteger wordIdx = 0; wordIdx < wordsPerThread && wordsPerThread * threadIdx + wordIdx < words.count; wordIdx++) {
// The String to convert
NSString *aWord = [words objectAtIndex:wordsPerThread * threadIdx + wordIdx];
// Some Exceptions to skip certain words.
if (...) {
continue;
}
// CoreData Conversion.
Word *toSaveWord = [NSEntityDescription insertNewObjectForEntityForName:#"Word" inManagedObjectContext:context];
[toSaveWord setCreated:creationDate];
[toSaveWord setText:aWord];
[toSaveWord addDictionariesObject:dictionary];
operationPerInterval++;
NSTimeInterval endInterval = [[NSDate date] timeIntervalSince1970];
// Update case.
if (endInterval - beginInterval > UPDATE_INTERVAL) {
NSLog(#"Thread %lu Processed %lu words", threadIdx, wordIdx);
// UI Update. It will be updated only by the first queue.
if (threadIdx == 0) {
// UI Update code.
}
beginInterval = endInterval;
operationPerInterval = 0;
}
}
// When the NSOperation goes to finish the CoreData thread context is saved.
[context save:nil];
NSLog(#"Operation %lu finished", threadIdx);
};
// Add the NSBlockOperation to queue.
[_operationsQueue addOperationWithBlock:threadBlock];
}
NSLog(#"Operations Added");
}
A few thoughts:
Setting max concurrent operations so high is not going to have much effect. It's unlikely to be more than two if you have two cores
It looks as though you're using the same NSManagedObjectContext for all your processes. This is Not Good
Let's assume that your max concurrent operations was 100. The bottle-neck would be the main thread, where you're trying to update a label for every operation. Try to update the main thread for every n records instead of every one
You shouldn't need to lock the context if you're using Core Data correctly... which means using a different context for each thread
You don't seem to ever save the context?
Batching operations is a good way to improve performance... but see previous point
As you suggest, there's an overhead in creating a GCD operation. Creating a new one for each word is probably not optimal. You need to balance the overhead of creating a new processes with the benefits of parallelisation
In short, threading is hard, even when you use something like GCD.
It's hard to way without measuring and profiling but what looks suspicious to me is your saving the full dictionary of words that have been saved so far with the save of each word. So the amount of data per save gets successively larger and larger.
// the dictionary at this point contains all words saved so far
// which each contains a full dictionary
[toSaveWord addDictionariesObject:dictionary];
// add each time so it gets bigger each time
[dictionary addWordsObject:toSaveWord];
So, each save is saving more and more data. Why save a dictionary of all words with each word?
Some other thoughts:
why build up coreDataWords that you never use?
I wonder if you're getting the concurrency you're since you're synchronizing the full block of work.
Things to try:
comment out the dictionary on the toSaveWord in addition to the dictionary you're building up and try again - see if it's your data/data structures or DB/coreData.
Do the first but also create a serial version of it to see if you're actually getting concurency benefits.

CoreData, NSManagedObject fetch or create if not exists

I am trying to parse a lot of text files and organize their contents as managed objects. There are a lot of duplicates in the text files, so one of the "collateral" tasks is to get rid of them.
What i am trying to do in this respect is to check whether an entity with the given content exists, and if it doesn't, i create one. However, i have different entities with different attributes and relationships. What i want is a kind of function that would take a number of attributes as an input and return a new NSManagedObject instance, and i wouldn't have to worry if it was inserted into the data store or fetched from it.
Is there one?
I must also say that i am a noob at core data.
Some more detail, if you want:
I am trying to write a sort of dictionary. I have words (Word{NSString *word, <<-> Rule rule}), rules (Rule{NSString name, <->>Word word, <<->PartOfSpeech partOfSpeech, <<-> Ending endings}), parts of speech (PartOfSpeech{NSString name, <<-> Rule rule}) (i hope the notation is clear).
Two words are equal, if they have the same word property, and "linked" to the same rule. Two rules are the same, if they have the same endings and part of speech.
So far i've written a method that takes NSPredicate, NSManagedObjectContext and NSEntityDescription as an input, and first queries the datastore and returns an entity if it finds one, or creates a new one, inserts it into the datastore and returns it. However, in this case I cannot populate the new entity with the necessary data (within that method), so i have to either pass an NSDictionary with the names of attributes and their values and insert them, or return by reference a flag as to whether i created a new object or returned an old one, so that i could populate it with the data outside.
But it looks kind of ugly. I'm sure there must be something more elegant than that, i just couldn't find it. Please, help me if you can.
Your basically on the right path. Core Data is an object graph. There not a lot of dynamic built in. There's also no "upsert". like you surmise, you have to fetch and if it doesn't exist, you insert one.
Here is what I have just started using to handle a fetch-or-create scenario. I am using a top level managed object which contains a few to-many relationships to subordinate objects. I have a class that houses a few arrays of data (those are not shown here). This class is responsible for saving and retrieving to and from core data. When the class is created, I do a fetch-or-create to access my top level NSManagedObject.
#implementation MyDataManagerClass
...
#synthesize MyRootDataMO;
- (MyDataManagerClass *) init {
// Init managed object
NSManagedObjectContext *managedObjectContext = [(MyAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
// Fetch or Create root user data managed object
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSError *error = nil;
NSArray *result = [managedObjectContext executeFetchRequest:request error:&error];
if (result == nil) {
NSLog(#"fetch result = nil");
// Handle the error here
} else {
if([result count] > 0) {
NSLog(#"fetch saved MO");
MyRootDataMO = (MyRootDataMO *)[result objectAtIndex:0];
} else {
NSLog(#"create new MO");
MyRootDataMO = (MyRootDataMO *)[NSEntityDescription insertNewObjectForEntityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
}
}
return self;
}
...

Resources