Saving data in background thread doesn't work - objective-c

I am trying to save my data in CoreData in background thread, but it crash or it doesnt work, any idea where am I doing the mistake?
When I try with main thread it works correctly, I tried to google to find some information in background thread, I couldn't find some useful article about it.
any help appreciate.
Thanks
This is the objective -c code, what I tried
-(void)insertNameAndSurnameInDataBase:(NSString *)name surname:(NSString *)surname numbers:(NSArray *)numbers labels:(NSArray *)labels{
NSManagedObjectContext *backgroundMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[backgroundMOC performBlock:^{
NSError *error;
NameAndSurname *nameAndSurname = [NSEntityDescription insertNewObjectForEntityForName:#"NameAndSurname" inManagedObjectContext:backgroundMOC];
NSString *nSurname = [NSString stringWithFormat:#"%# %#",name,surname];
nameAndSurname.nameSurname = nSurname;
if ([backgroundMOC save:&error]) {
}
else{
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"NameAndSurname"
inManagedObjectContext:backgroundMOC];
// predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"nameSurname =[c] %#", nSurname];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSArray *fetchedObjects = [backgroundMOC executeFetchRequest:fetchRequest error:&error];
if([fetchedObjects count] > 0){
[numbers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
id obj2 = [labels objectAtIndex:idx];
obj = [obj stringByReplacingOccurrencesOfString:#" " withString:#""];
ContactNumber *phoneNumber = [NSEntityDescription insertNewObjectForEntityForName:#"ContactNumber" inManagedObjectContext:backgroundMOC];
phoneNumber.number = obj;
phoneNumber.label = obj2;
phoneNumber.nameAndSurname = fetchedObjects[0];
NSError *error;
if ([backgroundMOC save:&error]) {
}
else{
}
}];
}
}];
}

Related

Predicate search not working (core data)

When inserting in the "FilterProperty" entity, I use the following code in a category "FilterProperty+Manage.h"
+(FilterProperty *)insertFilterPropertyWithName:(NSString *)filterPropertyName forManagedObjectContext:(NSManagedObjectContext *)managedObjectContext forFriendlyName:(NSString *)friendlyName
forDefaultValue:(NSNumber *)defaultValue forMinValue:(NSNumber *)minValue forMaxValue:(NSNumber *)maxValue forType:(NSNumber *)type forBelongsToFilter:(Filter *)filter
{
FilterProperty *fp = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"FilterProperty" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name = %# AND belongsToFilter.name = %#", filterPropertyName, filter.name];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil)
{
return nil;
}
if (fetchedObjects != nil && fetchedObjects.count > 0)
{
fp = fetchedObjects.firstObject;
}
else if(fetchedObjects != nil && fetchedObjects.count == 0)
{
fp = [NSEntityDescription insertNewObjectForEntityForName:#"FilterProperty" inManagedObjectContext:managedObjectContext];
fp.name = filterPropertyName;
fp.friendlyName = friendlyName;
fp.defaultValue = defaultValue;
fp.minValue = minValue;
fp.maxValue = maxValue;
fp.belongsToFilter = filter;
fp.type = type;
}
return fp;
}
No while searching, I do the following:
+(NSArray *)getAllFilterPropertiesForFilter:(Filter *)filter forManagedObjectContext (NSManagedObjectContext *)managedObjectContext
{
NSArray *set = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"FilterProperty" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"belongsToFilter.name = %#", filter.name];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
return nil;
}
if(fetchedObjects != nil)
{
set = fetchedObjects;
}
return set;
}
The above function when called for any given filter is returning an array with zero objects. I am not able to get why. Any help would be appreciated! Thanks.
You try out this code for searching:-
+(NSArray *)getAllFilterPropertiesForFilter:(Filter *)filter forManagedObjectContext (NSManagedObjectContext *)managedObjectContext
{
NSArray *set = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"FilterProperty" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"belongsToFilter.name == %#", filter.name];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
return nil;
}
if(fetchedObjects != nil)
{
set = fetchedObjects;
}
return set;
}
I think in your insertFilterPropertyWithName method you are not saving the new data. If I am correct, try this code after your if-else block:
NSError *error;
if (![managedObjectContext save:&error])
{
//could not save
}
Hope this could resolve your problem.
In that below if condition you need to save in managedobjectcontext.
else if(fetchedObjects != nil && fetchedObjects.count == 0)
{
fp = [NSEntityDescription insertNewObjectForEntityForName:#"FilterProperty" inManagedObjectContext:managedObjectContext];
fp.name = filterPropertyName;
fp.friendlyName = friendlyName;
fp.defaultValue = defaultValue;
fp.minValue = minValue;
fp.maxValue = maxValue;
fp.belongsToFilter = filter;
fp.type = type;
NSError *err = nil;
[managedObjectContext save:&err];
if (errorInWrite != nil) {
//Problem while saving
}
}
Use like instead of "=" Hope it will be useful.
+(NSArray *)getAllFilterPropertiesForFilter:(Filter *)filter forManagedObjectContext (NSManagedObjectContext *)managedObjectContext
{
NSArray *set = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"FilterProperty" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"belongsToFilter.name like %#", filter.name];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
return nil;
}
if(fetchedObjects != nil)
{
set = fetchedObjects;
}
return set;
}

NSFetchedResultsController, Core data and Threading

Hello fellow overflowers
I have a hard time figuring out how to fetch NSManagedObjects in a background thread and then show the results via a NSFetchedResultsController.
This is my code so far:
_theManagedObjectContext = [[DataManager sharedInstance] mainManagedObjectContext];
__block NSMutableArray *objectsIDs;
[[[DataManager sharedInstance] backgroundManagedObjectContext] performBlock:^{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"Ret" inManagedObjectContext:[[DataManager sharedInstance] backgroundManagedObjectContext]];
NSArray *results = [[[DataManager sharedInstance] backgroundManagedObjectContext] executeFetchRequest:fetchRequest error:nil];
for (Ret *ret in results) {
NSManagedObjectID *moID = [ret objectID];
[objectsIDs addObject:moID];
NSLog(#"%#", objectsIDs);
}
[[[DataManager sharedInstance] mainManagedObjectContext ] performBlock:^{
[self loadDishesWithObjectIDs:objectsIDs];
}];
}];
First I fetch all the objects in a background thread and then transfering the NSMangedObjectIDs to the main thread.
In my "loadDishes" method:
- (void)loadDishesWithObjectIDs:(NSArray *)objectsIDs {
/*
[NSFetchedResultsController deleteCacheWithName:#"dishes"];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:**???**? managedObjectContext:_theManagedObjectContext sectionNameKeyPath:nil cacheName:#"dishes"];
_fetchedResultsController.delegate = self;
NSError *error = nil;
if (![_fetchedResultsController performFetch:&error]) {
NSLog(#"Fetch Failed");
}
NSArray *theDishes = _fetchedResultsController.fetchedObjects;*/
}
How would I manage to show the objects with NSFetchResultscontroller by the ObjectIDs which is fetched from the background thread ?
Thank you in advance :)
You would need to create a fetch request that looks something like this
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"Ret"
inManagedObjectContext:[[DataManager sharedInstance] mainManagedObjectContext]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"self IN %#", objectIDs];

Recipes to update database in Core Data

I have a method for adding stamp to the card, from QR code reader to web service
my question is,
Here is my code:
- (void)addStamp
{
NSString *url = [_URL stringByAppendingString:#"add-stamp"];
NSString *post = [NSString stringWithFormat:#"userId=%#&code=%#", self.userId, self.code ];
post = [post stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSData *postData = [NSData dataWithBytes:[post UTF8String]
length:[post lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL
URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:600];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:postData];
}
Rightnow I want to update my DB with new method -> if the code is repeated add stamp, if not create add a new card:
-(void)addStampInDB:(int)cardId
{
NSManagedObjectContext* managedObjectContext = [(AppDelegate*) [[UIApplication
sharedApplication] delegate] managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Card"
inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"remote_id = %d", cardId];
[fetchRequest setPredicate:predicate];
[managedObjectContext lock];
NSError *error;
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest
error:&error];
[managedObjectContext unlock];
Card *d = nil;
if([fetchedObjects count] > 0 ){
d = [fetchedObjects objectAtIndex:0];
cardId++;
} else{
d = [NSEntityDescription insertNewObjectForEntityForName:#"Card"
inManagedObjectContext:managedObjectContext];
}
NSArray *test;
for(NSDictionary *stamp in test)
{
d.remote_id = [NSNumber numberWithInt:[[stamp objectForKey:#"id"] intValue]];
d.stampNumber = [NSNumber numberWithInt:[[stamp objectForKey:#"stampNumber"] intValue]];
d.createdAt = [NSDate dateWithTimeIntervalSince1970:[[stamp objectForKey:#"createdAt"]
intValue]];
[managedObjectContext lock];
NSError *error;
if (![managedObjectContext save:&error])
{
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
NSLog(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
} else {
NSLog(#" %#", [error userInfo]);
}
}
[managedObjectContext unlock];
}
}
I don't know if I'm in a right way or not and also how I can test my method?
I'm not very sure about your question but I'll try to give some hints.
First
Card *d = nil;
if([fetchedObjects count] > 0 ){
d = [fetchedObjects objectAtIndex:0];
cardId++;
} else{
d = [NSEntityDescription insertNewObjectForEntityForName:#"Card"
inManagedObjectContext:managedObjectContext];
}
Here you have found an existing card, you use it, else you create a new. Question: what about cardId? Where is it used?
Second
for(NSDictionary *stamp in test)
{
d.remote_id = [NSNumber numberWithInt:[[stamp objectForKey:#"id"] intValue]];
d.stampNumber = [NSNumber numberWithInt:[[stamp objectForKey:#"stampNumber"] intValue]];
d.createdAt = [NSDate dateWithTimeIntervalSince1970:[[stamp objectForKey:#"createdAt"]
intValue]];
// other code here
}
If you loop test, you overwrite the value of your card d each time. Is it right?
Third
for(NSDictionary *stamp in test)
{
// other code here
}
// save here
Move the save out of the for loop. This boost performances in your app. It is not necessary to save each time in your loop. Do it at the end only.
Hope that helps.
P.S. If you want to know something else let me know.

Is NSFetchRequest with an NSPredicate containing a NSManagedObjectID safe to pass across thread boundaries?

If I've created an NSFetchRequest on the main thread like so:
NSManagedObject *bar = ...;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Foo"];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"SELF.bar == %#",
[bar objectID]]];
Is it safe to pass this NSFetchRequest with an NSPredicate that contains a NSManagedObjectID to a background thread like so?
NSManagedObject *bar = nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Foo"];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"SELF.bar == %#",
[bar objectID]]];
NSPersistentStoreCoordinator *persistentStoreCoordinator = ...;
[[NSBlockOperation blockOperationWithBlock:^{
NSManagedObjectContext *managedObjectContext = [NSManagedObjectContext new];
[managedObjectContext setPersistentStoreCoordinator:persistentStoreCoordinator];
NSArray *foos = [managedObjectContext executeFetchRequest:fetchRequest
error:NULL];
}] start];
I found some example code in the CoreData release notes for iOS 5 that pretty much does this, so it looks ok.
NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:#"Entity"];
__block NSUInteger rCount = 0;
[context performBlockAndWait:^() {
NSError *error;
rCount = [context countForFetchRequest:fr error:&error];
if (rCount == NSNotFound) {
// Handle the error.
}
}];
NSLog(#"Retrieved %d items", (int)rCount);

NSLog and Core Data

I have three Classes for which I am extending NSManagedObject (I know this isn't required but I wanted the property notation). Theses Classes are QuestionSet, Question and Answer. My data model is setup so that there's a many to many between them like so: QuestionSet <<->> Question <<->> Answer. I load up everything from a server and it save successfully meaning I look in the .db file and it's all how I expect, if I immediately NSLog the array questionsets they look great and if I go take a quiz it works great.
However if I NSLog that array anywhere but right after where I load them from the server, it crashes my app. The quiz still runs fine so I know the data is in there and in the expected format. Does this have something to do with Core Data clearing out space and then my relationships don't 'lazy loaded' when I'm just trying to log them? Sorry, still wrapping my head around core data.
This bit works, I load from the server and at the bottom I 'loadTrivia' and log what I get. The issue comes if at sometime later during the course of the app execution, an NSLog of the questionsets will fail.
- (void)loadQuestionSetFromNetworkWithID:(NSNumber *)p_id andTitle:(NSString *)p_title
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:kTriviaQuestionSetURL, [p_id intValue]]]];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
NSArray *questions = [[[sbjson objectWithString:json_string error:nil] valueForKeyPath:#"Q"] objectAtIndex:0];
NSError *error;
NSFetchRequest *questionsetRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *questionsetEntity = [NSEntityDescription entityForName:#"QuestionSet" inManagedObjectContext:[self managedObjectContext]];
NSPredicate *questionsetPredicate = [NSPredicate predicateWithFormat:#"id = %d", [p_id intValue]];
[questionsetRequest setEntity:questionsetEntity];
[questionsetRequest setPredicate:questionsetPredicate];
QuestionSet *qs = nil;
NSArray *questionSetObjects = [[self managedObjectContext] executeFetchRequest:questionsetRequest error:&error];
if (questionSetObjects == nil){
// Handle errors
}
if ([questionSetObjects count] > 0)
qs = [questionSetObjects objectAtIndex:0];
else
qs = [NSEntityDescription insertNewObjectForEntityForName:#"QuestionSet" inManagedObjectContext:[self managedObjectContext]];
qs.id = p_id;
qs.title = p_title;
for (int i = 0; i < [questions count]; i++)
{
NSFetchRequest *questionRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *questionEntity = [NSEntityDescription entityForName:#"Question" inManagedObjectContext:[self managedObjectContext]];
NSPredicate *questionPredicate = [NSPredicate predicateWithFormat:#"(id = %d)", [[[questions objectAtIndex:i] objectForKey:#"id"] intValue]];
[questionRequest setEntity:questionEntity];
[questionRequest setPredicate:questionPredicate];
Question *q = nil;
NSArray *questionObjects = [[self managedObjectContext] executeFetchRequest:questionRequest error:&error];
if (questionObjects == nil){
// Handle errors
}
if ([questionObjects count] > 0)
q = [questionObjects objectAtIndex:0];
else
q = [NSEntityDescription insertNewObjectForEntityForName:#"Question" inManagedObjectContext:[self managedObjectContext]];
q.id = [NSNumber numberWithInt:[[[questions objectAtIndex:i] objectForKey:#"id"] intValue]];
q.question = [[questions objectAtIndex:i] objectForKey:#"text"];
q.answer = [NSNumber numberWithInt:[[[questions objectAtIndex:i] objectForKey:#"ca"] intValue]];
for (int j = 0; j < [[[questions objectAtIndex:i] objectForKey:#"A"] count]; j++)
{
NSFetchRequest *answerRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *answerEntity = [NSEntityDescription entityForName:#"Answer" inManagedObjectContext:[self managedObjectContext]];
NSPredicate *answerPredicate = [NSPredicate predicateWithFormat:#"(id = %d)", [[[[[questions objectAtIndex:i] objectForKey:#"A"] objectAtIndex:j] objectForKey:#"id"] intValue]];
[answerRequest setEntity:answerEntity];
[answerRequest setPredicate:answerPredicate];
Answer *a = nil;
NSArray *answerObjects = [[self managedObjectContext] executeFetchRequest:answerRequest error:&error];
if (answerObjects == nil){
// Handle errors
}
if ([answerObjects count] > 0)
a = [answerObjects objectAtIndex:0];
else
a = [NSEntityDescription insertNewObjectForEntityForName:#"Answer" inManagedObjectContext:[self managedObjectContext]];
a.id = [NSNumber numberWithInt:[[[[[questions objectAtIndex:i] objectForKey:#"A"] objectAtIndex:j] objectForKey:#"id"] intValue]];
a.answer = [[[[questions objectAtIndex:i] objectForKey:#"A"] objectAtIndex:j] objectForKey:#"text"];
a.questions = [a.questions setByAddingObject:q];
q.answers = [q.answers setByAddingObject:a];
[answerRequest release];
}
q.questionsets = [q.questionsets setByAddingObject:qs];
qs.questions = [qs.questions setByAddingObject:q];
[questionRequest release];
}
[questionsetRequest release];
[[self managedObjectContext] save:&error];
[json_string release];
[self loadTrivia];
NSLog(#"After Load: %#", self.questionsets);
}
This function fetches all the QuestionSet objects from the db and stores the array in an ivar.
- (BOOL)loadTrivia
{
NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"QuestionSet" inManagedObjectContext:[self managedObjectContext]];
[request setEntity:entity];
self.questionsets = [[self managedObjectContext] executeFetchRequest:request error:&error];
return YES;
}
Just for completeness, here are my description methods in my classes
from QuestionSet.h
- (NSString *)description
{
return [NSString stringWithFormat:#"\
\n\tid: %#\
\n\ttitle: %#\
\n\tquestions: %#\n",
self.id,
self.title,
self.questions];
}
from Question.h
- (NSString *)description
{
return [NSString stringWithFormat:#"\
\n\tid: %#\
\n\tanswer: %#\
\n\tquestion: %#\
\n\tanswers: %#\n",
self.id,
self.answer,
self.question,
self.answers];
}
from Answer.h
- (NSString *)description
{
return [NSString stringWithFormat:#"\
\n\tid: %#\
\n\tanswer: %#\n",
self.id,
self.answer];
}
You shouldn't subclass the description method (Link):
Although the description method does not cause a fault to fire, if you implement a custom description method that accesses the object’s persistent properties, this will cause a fault to fire. You are strongly discouraged from overriding description in this way.
I tested your loadTrivia code (with a different entityName) on my app and it worked fine.
Did you have a look at the NSError (you should initialize it with =nil):
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
Also you should release the NSFetchRequest after execution in - (BOOL)loadTrivia.