Inconsistency in Saving/Fetching Data in Core Data - ios

My first post here, getting straight to the point.
I'm facing a problem and I'm not sure whether is with saving of data or fetching it.
Here are my codes where I tried to save the data in my Entity, "Sequence". The entity consists of 2 attributes "seqForWk1CD" & "seqForWk2CD".
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
SequenceMO *seqEntity = [NSEntityDescription
insertNewObjectForEntityForName:#"Sequence"
inManagedObjectContext:context];
// some other code
seqEntity.seqForWk1CD = arrForWk1;
seqEntity.seqForWk2CD = arrForWk2;
NSLog(#"%#", arrForWk1);
NSLog(#"%#", arrForWk2);
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
When printed, the arrays will always display the contents of the array.
This is where I try to fetch the data.
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Sequence" inManagedObjectContext:context];
[request setEntity:entity];
NSError *error;
if(![context save:&error]){
NSLog(#"Error fetching. %# %#", error, [error localizedDescription]);
}
NSUInteger count = [context countForFetchRequest:request error:&error];
if(count != 0){
NSArray *fetchObj = [context executeFetchRequest:request error:&error];
NSManagedObject *sequence = (NSManagedObject *)[fetchObj objectAtIndex:0];
NSLog(#"1 - %#", sequence);
arrForWk1 = [sequence valueForKey:#"seqForWk1CD"];
NSLog(#"%#", arrForWk1);
arrForWk2 = [sequence valueForKey:#"seqForWk2CD"];
NSLog(#"%#", arrForWk2);
}
The problem comes when I restart the application. The arrays either show (null) for both arrays or it shows the contents of both of the arrays. The if statement for if(![context save:&error]) never gets triggered. Subclasses of NSManagedObject for the entity has already been added.I've also tried declaring the AppDelegate in #interface and forced to save the context immediately by doing [delegate saveContext];.Here is the method where the saving happens. "check" is initialized to 0 at the viewDidLoad method. Both "arrForWk1" & "arrForWk2" are declared at #interface.
- (IBAction)randomizeSequence:(UIButton *)sender {
NSMutableArray *storeArray = [[NSMutableArray alloc] init];
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
SequenceMO *seqEntity = [NSEntityDescription insertNewObjectForEntityForName:#"Sequence" inManagedObjectContext:context];
BOOL record = NO;
int x;
for (int i=0; [storeArray count] < 9; i++) //Loop for generate different random values
{
x = 1 + arc4random() % 9;//generating random number
if(i==0)//for first time
{
[storeArray addObject:[NSNumber numberWithInt:x]];
}
else
{
for (int j=0; j<= [storeArray count]-1; j++)
{
if (x ==[[storeArray objectAtIndex:j] intValue])
record = YES;
}
if (record == YES)
{
record = NO;
}
else
{
[storeArray addObject:[NSNumber numberWithInt:x]];
}
}
}
check++;
if(check == 1 ) {
arrForWk1 = storeArray;
[self.wk1Seq reloadData];
}
else if(check == 2) {
arrForWk2 = storeArray;
seqEntity.seqForWk1CD = arrForWk1;
seqEntity.seqForWk2CD = arrForWk2;
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[self.wk2Seq reloadData];
}
Just to add on, the arrays are of data type NSMutableArray and I'm trying to store them into attributes of type "Transformable".

After researching around I managed to solve the problem. I'm not sure how it actually works but I solved it by rearranging the code when I'm trying to fetch the data.
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
NSEntityDescription *descriptor = [NSEntityDescription entityForName:#"Sequence" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
request.entity = descriptor;
NSError *error;
NSArray *fetchObj = [context executeFetchRequest:request error:&error];
if(fetchObj == nil) {
NSLog(#"Error occured when trying to fetch.");
}
else {
if(fetchObj.count == 0) {
NSLog(#"No objects saved");
else {
NSManagedObject *sequence = (NSManagedObject *)[fetchObj objectAtIndex:0];
NSLog(#"1 - %#", sequence);
arrForWk1 = [sequence valueForKey:#"seqForWk1CD"];
NSLog(#"%#", arrForWk1);
arrForWk2 = [sequence valueForKey:#"seqForWk2CD"];
NSLog(#"%#", arrForWk2);
NSLog(#"2 - %#", sequence);
}
I've tried also tried 2 ways of saving the data. In the if statement where I tried to save the data, I converted the NSMutableArrays to NSArrays.
else if(check == 2) {
test2 = storeArray;
NSManagedObjectContext *context = [delegate managedObjectContext];
SequenceMO *seqEntity = [NSEntityDescription insertNewObjectForEntityForName:#"Sequence" inManagedObjectContext:context];
NSArray *tArr = [arrForWk1 copy];
NSArray *tArr2 = [arrForWk2 copy];
seqEntity.seqForWk1CD = tArr;
seqEntity.seqForWk2CD = tArr2;
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
The second way I tried is by using an answer from Core Data not saving changes to Transformable property
id temp = [seqEntity seqForWk1CD];
id temp2 = [seqEntity seqForWk2CD];
temp = arrForWk1;
temp2 = arrForWk2;
[seqEntity setSeqForWk1CD:temp];
[seqEntity setSeqForWk2CD:temp2];
Apparently it worked somehow.

Related

Only Able to Save The First Object

A problem I'm facing right now is that when I try to save objects, Core Data appears to only save the first one.
Code to save objects
- (void)savingResCore {
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
ResultMO *resEntity = [NSEntityDescription insertNewObjectForEntityForName:#"Result" inManagedObjectContext:context];
if(indCheck == 1) {
resEntity.result1 = pena1;
resEntity.resuName1 = penaNam1;
}
if(indCheck == 2) {
resEntity.result2 = pena2;
resEntity.resuName2 = penaNam2;
}
if(indCheck == 3) {
resEntity.result3 = pena3;
resEntity.resuName3 = penaNam3;
}
_seqNameChk = [NSNumber numberWithInt:[_seqNameChk intValue] - 1];
if([_seqNameChk isEqual:#(0)]){
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
}
Code to fetch objects
- (void)fetchResCore {
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
NSEntityDescription *descriptor = [NSEntityDescription entityForName:#"Result" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
request.entity = descriptor;
NSError *error;
NSArray *contArr = [context executeFetchRequest:request error:&error];
if(contArr == nil){
NSLog(#"Problems fetching data.");
}
else if(contArr != nil) {
if(contArr.count == 0) {
NSLog(#"No objects saved");
}
else {
NSManagedObject *resu = (NSManagedObject *)[contArr objectAtIndex:0];
NSLog(#"1 - %#", resu);
pena1 = [resu valueForKey:#"result1"];
penaNam1 = [resu valueForKey:#"resuName1"];
NSLog(#"%#", pena1);
pena2 = [resu valueForKey:#"result2"];
penaNam2 = [resu valueForKey:#"resuName2"];
NSLog(#"%#", pena2);
pena3 = [resu valueForKey:#"result3"];
penaNam3 = [resu valueForKey:#"resuName3"];
NSLog(#"%#", pena3);
NSLog(#"2 - %#", resu);
}
}
}
For example, I have a button that increments indCheck by one each time it is pressed and each time it calls up the fetchResCore method. Assume seqNameChk = 2. For the first time, the attributes result1 & resuName1 gets saved successfully. After the button is pressed consecutively, it no longer saves data. Hence, attributes result2, resuName2, result3 & resuName3 does not contain data when retrieved at the fetchResCore method.
Result printed in the console for resu
resuName1 = "(\n Alan\n)";
resuName2 = nil;
resuName3 = nil;
result1 = "(\n 100\n)";
result2 = nil;
result3 = nil;
Result printed in the console for contArr
result1 = \"(\\n 100\\n)\";\n result2 = nil;\n result3 = nil;\n
resuName1 = \"(\\n Alan\\n)\";\n resuName2 = nil;\n resuName3 = nil;\n
As Jon Rose had pointed out,
I would assume that they are all in the array contArr but you are only looking at the first one: NSManagedObject *resu = (NSManagedObject *)[contArr objectAtIndex:0];
I printed out contArr.count and it resulted in a 3. I've also printed out the contents of the array by using a for loop and again, 3 elements which were populated, was displayed.
To solve the problem, I had to rectify [contArr objectAtIndex:0] as it only displayed the first element in the array.
In the fetchResCore method
for(int i = 0; i < contArr.count; i++){
NSManagedObject *resu = (NSManagedObject *)[contArr objectAtIndex:i];
NSLog(#"1 - %#", resu);
if(pena1.count == 0 && penaNam1.count == 0){
pena1 = [resu valueForKey:#"result1"];
penaNam1 = [resu valueForKey:#"resuName1"];
NSLog(#"%#", pena1);
}
if(pena2.count == 0 && penaNam2.count == 0){
pena2 = [resu valueForKey:#"result2"];
penaNam2 = [resu valueForKey:#"resuName2"];
NSLog(#"%#", pena2);
}
if(pena3.count == 0 && penaNam3.count == 0){
pena3 = [resu valueForKey:#"result3"];
penaNam3 = [resu valueForKey:#"resuName3"];
NSLog(#"%#", pena3);
}
}
Thanks again, Jon Rose for pointing out a simple mistake that took me days to figure out.

CoreData objects not saved between screens

I have a screen that holds a UITableView, in this screen I have an array of NSManagedObjects. It's working just fine, but as I try move to another screen (click on a specific cell, and push a new screen), then return to the same UITableView screen, all the objects got lost.
What does it means? I try to print the array of the NSManagedObjects and it's fine, all the objects there, but as I print the description of each object, I get nil from all the object attributes.
Someone knows whats the cause of it? I don't know why but it worked just fine 12 hours ago, but now it's all messed up and I don't have a clue what have I done.
Thanks in advance!
Save method:
- (void)saveContext {
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
NSError *error = nil;
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
else {
NSLog(#"Context saved!");
}
}
}
This is how I save the objects:
NSDictionary *response = responseObject;
if ([[response valueForKey:#"status"] rangeOfString:#"ok"].location != NSNotFound)
{
NSArray *data = [response objectForKey:#"data"];
if (data.count != 0)
{
if (page.integerValue == 0) {
[[DownloadData sharedData] deleteAllObjectsFromEntityName:#"DbHomeCuisine"];
[[DownloadData sharedData] deleteAllObjectsFromEntityName:#"DbHomeCategory"];
[[DownloadData sharedData] deleteAllObjectsFromEntityName:#"DbHomeDish"];
}
NSMutableArray *homePageObjects = [[NSMutableArray alloc] initWithCapacity:data.count];
for (NSDictionary *object in data)
{
NSNumber *type = [object objectForKey:#"type"];
switch (type.integerValue) {
case 1:
{
NSDictionary *content = [object objectForKey:#"content"];
NSManagedObjectContext *context = [[MainDb sharedDb] managedObjectContext];
DbHomeCuisine *homeCuisine = [NSEntityDescription insertNewObjectForEntityForName:#"DbHomeCuisine" inManagedObjectContext:context];
NSInteger cuisineId = [[content valueForKey:#"cuisine_id"] integerValue];
homeCuisine.cuisine = [self gCuisineWithCuisineId:[NSNumber numberWithInteger:cuisineId]];
NSInteger count = [[content valueForKey:#"count"] integerValue];
homeCuisine.count = [NSNumber numberWithInteger:count];
homeCuisine.type = type;
[homePageObjects addObject:homeCuisine];
}
break;
case 2:
{
NSDictionary *content = [object objectForKey:#"content"];
NSManagedObjectContext *context = [[MainDb sharedDb] managedObjectContext];
DbHomeCategory *homeCategory = [NSEntityDescription insertNewObjectForEntityForName:#"DbHomeCategory" inManagedObjectContext:context];
NSInteger categoryId = [[content valueForKey:#"category_id"] integerValue];
homeCategory.category = [self gCategoryWithCategoryId:[NSNumber numberWithInteger:categoryId]];
NSInteger count = [[content valueForKey:#"count"] integerValue];
homeCategory.count = [NSNumber numberWithInteger:count];
homeCategory.type = type;
[homePageObjects addObject:homeCategory];
}
break;
case 3:
{
NSDictionary *content = [object objectForKey:#"content"];
NSManagedObjectContext *context = [[MainDb sharedDb] managedObjectContext];
DbHomeDish *homeDish = [NSEntityDescription insertNewObjectForEntityForName:#"DbHomeDish" inManagedObjectContext:context];
homeDish.dishId = [self gInt:content forKey:#"dish_id"];
homeDish.headline = [AppUtils checkForEmptyValue:[content valueForKey:#"title"]];
homeDish.text = [AppUtils checkForEmptyValue:[content valueForKey:#"description"]];
homeDish.cuisineId = [self gInt:content forKey:#"cuisine_id"];
homeDish.cuisine = [self gCuisineWithCuisineId:homeDish.cuisineId];
homeDish.creationDate = [AppUtils checkForEmptyValue:[content valueForKey:#"creation_time"]];
homeDish.userId = [self gInt:content forKey:#"user_id"];
homeDish.longitude = [self gDouble:content forKey:#"lng"];
homeDish.latitude = [self gDouble:content forKey:#"lat"];
homeDish.lastPromoteDate = [AppUtils checkForEmptyValue:[content valueForKey:#"last_promote_time"]];
homeDish.price = [self gInt:content forKey:#"price"];
homeDish.currency = [AppUtils checkForEmptyValue:[content valueForKey:#"currency"]];
homeDish.countryName = [AppUtils checkForEmptyValue:[content valueForKey:#"country_name"]];
homeDish.baseCurrency = [self gFloat:content forKey:#"base_currency"];
homeDish.exchangeRate = [self gFloat:content forKey:#"exchange_rate"];
homeDish.countryIsoCode = [AppUtils checkForEmptyValue:[content valueForKey:#"country_iso_code"]];
homeDish.mainPhoto = [AppUtils checkForEmptyValue:[content valueForKey:#"main_photo"]];
homeDish.like = [self gLikeWithDishId:homeDish.dishId];
homeDish.profileImageURL = [AppUtils checkForEmptyValue:[content valueForKey:#"profile_img_url"]];
homeDish.likeCount = [self gInt:content forKey:#"likes"];
homeDish.type = type;
[homePageObjects addObject:homeDish];
}
break;
default:
break;
}
}
// ##log -- Save data to core data and device
//
//
[[MainDb sharedDb] saveContext];
if (success) {
success(operation, homePageObjects);
}
}
}
Seriously, you should consider refactoring using a NSFetchedResultsController. Start from the template provided in Xcode (New Project -> Master/Detail -> check Core Data, the code is in MasterViewController.m).
I strongly discourage loading Core Data objects into an array to be displayed in a table view. Your problem is typical for such a setup, and you will run into memory and performance issues eventually as well.

Core Data not saving an incremented integer

I'm making a SpriteKit game and I need to save the player's score using Core Data. I have a property with int value that starts off as being set to "5" and increment it x amount of times. I save it then transition to a different scene and fetch it. It shows up un-incremented with the initial value of "5".
I'm new to Core Data so forgive me if this is a stupid question, but how can I get Core Data to take the incrementation in to account? Or Is information being lost when I reference the property and how can I prevent this?
self.score = 5;
self.score++
and then save by calling this method.
AppDelegate.m
-(void) createObject {
Score *scoreEntity = (Score *)[NSEntityDescription
insertNewObjectForEntityForName:#"Score"
inManagedObjectContext:self.managedObjectContext];
SpaceshipScene *spaceshipSceneReference = [[SpaceshipScene alloc] init];
id points = [NSNumber numberWithInteger: spaceshipSceneReference.score];
scoreEntity.points = points;
scoreEntity.playerName = #"Joe";
NSError *error = nil;
// Saves the managedObjectContext
if (! [[self managedObjectContext] save:&error] ) {
NSLog(#"An error! %#", error);
}
}
This is how I call it.
SpaceshipScene.m
AppDelegate *appDelegateReference = [[AppDelegate alloc] init];
[appDelegateReference createObject];
I then fetch it in another class/scene using this method
AppDelegate.m
-(void)fetchObject {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Score"inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Sort fetched data
NSSortDescriptor *sortByPoints = [[NSSortDescriptor alloc] initWithKey:#"points" ascending:NO];
// Put them in an array
NSArray *sortDescriptor = [[NSArray alloc] initWithObjects:sortByPoints, nil];
// Pass the array to the fetch request
[fetchRequest setSortDescriptors:sortDescriptor];
NSError *error = nil;
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"Problem %#", error);
}
for (Score *s in fetchedObjects) {
NSLog(#" %# %d",s.playerName, [s.points integerValue]);
}
}
This is how I call it in the final scene/class
AppDelegate *appDelegateReference = [[AppDelegate alloc] init];
[appDelegateReference fetchObject];
Hope this will work, after fetching your Score entity , simply assign new values in it don't use insert object for update any value.
[scoreEntity setPoints:[NSNumber numberWithInteger: spaceshipSceneReference.score]];
[scoreEntity setPlayerName:#"Joe"];
then Save the values:
NSError *error = nil;
// Saves the managedObjectContext
if (! [[self managedObjectContext] save:&error] ) {
NSLog(#"An error! %#", error);
}

Core Data updating relationship entity bug with concurrency

So i implemented Core Data Concurrency using http://www.cocoanetics.com/2012/07/multi-context-coredata/ i implemented separate private MOC for every object to input data, it works good, i have mainMOC on main thread fetching data using FRC, the only issue that i have faced so far is updating relationship entity.
so if i have Photographer & Photos entities one to many relationship "one photographer can have multiple photos", when user uploads new photo, i update photo entity and photographer "relationship" with the below code... 'note: this is a temp code'
+ (Photo *)didUploadNewPhoto:(PhotoClass *)photoObj inManagedObjectContext:(NSManagedObjectContext *)context
{
Photo *photo = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photos"];
request.predicate = [NSPredicate predicateWithFormat:#"photoID = %#",photoObj.ID];
NSError *error;
NSArray *matches = [context executeFetchRequest:request error:&error];
if (!matches || error || ([matches count] > 1)) {
//handle error
} else if ([matches count]) {
photo = [matches firstObject];
} else {
photo.title = #"";
photo.timeStamp = #"";
photo.photographer = [Photographer updatePhotographerClass:photoObj inManagedObjectContext:context];
NSError *error = nil;
if (![context save:&error]) {
//handle Error
}
return photo;
}
+ (Photographer *)updatePhotographerClass:(Photo *)photoObj inManagedObjectContext:(NSManagedObjectContext *)context
{
Photographer *photographer = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photographer"];
request.predicate = [NSPredicate predicateWithFormat:#"PhotographerID= %#",photoObj.PhotographerID];
NSError *error;
NSArray *matches = [context executeFetchRequest:request error:&error];
if (!matches || error || ([matches count] > 1)) {
//handle error
} else if ([matches count]) {
photographer = [matches firstObject];
photographer.didUploadPhoto = #YES;
}
return photographer;
}
The issue i'm facing is that FRC does update photo however, it doesn't update the boolean in Photographer "didUploadPhoto", is there something that I might be missing? if i use this same code using context on main thread, it works great... but if i change it to input data using background thread, it does update Photo but it doesn't update Photographer.
Update:
This is example code of how i call UploadPhoto method using background/private MOC
NSManagedObjectContext *tempContext = self.contextStore.newPrivateContext;
[tempContext performBlock:^{
[Photos didUploadNewPhoto:photoObj inManagedObjectContext:tempContext];
}];
and this is how i create privateContext
- (NSManagedObjectContext*)newPrivateContext
{
NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.persistentStoreCoordinator = self.persistentStoreCoordinator;
[context setUndoManager:nil];
return context;
}
Here is the notification that i use to merge changes...
- (void)setupSaveNotification
{
[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification
object:nil
queue:nil
usingBlock:^(NSNotification* note) {
NSManagedObjectContext *moc = self.mainContext;
if (note.object != moc) {
[moc performBlock:^(){
[moc mergeChangesFromContextDidSaveNotification:note];
}];
}
}];
}

Writing to coredata, even when done on background thread , blocks the UI considerably

Iam trying out a chat based app. I have setup a singleton coredata manager as follows
#import "CGSharedCoreData.h"
CGSharedCoreData *_cd;
#implementation CGSharedCoreData
#synthesize managedObjectModel = managedObjectModel_;
#synthesize managedObjectContext = managedObjectContext_;
#synthesize persistentStoreCoordinator = persistentStoreCoordinator_;
+ (CGSharedCoreData *)sharedCoreData{
static CGSharedCoreData *_cd = nil;
static dispatch_once_t onceCoreDataShared;
dispatch_once(&onceCoreDataShared, ^{
_cd = [[CGSharedCoreData alloc] init];
});
return _cd;
}
+ (void)saveContext:(NSManagedObjectContext*)c{
#try {
if (c.persistentStoreCoordinator.persistentStores.count == 0)
{
// This is the case where the persistent store is cleared during a logout.
CGLog(#"saveContext: PersistentStoreCoordinator is deallocated.");
return;
}
// Register context with the notification center
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
CGSharedCoreData *sharedCoreData = [CGSharedCoreData sharedCoreData];
[nc addObserver:sharedCoreData
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:c];
NSError *error = nil;
if (c != nil) {
CGLog(#"thread &&*&(*(* %d",[NSThread isMainThread]);
if ([c hasChanges] && ![c save:&error]) {
CGLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
[nc removeObserver:sharedCoreData name:NSManagedObjectContextDidSaveNotification
object:c];
}
#catch (NSException *exception) {
CGLog(#"***** Unresolved CoreData exception %#", [exception description]);
}
}
+ (dispatch_queue_t) backgroundSaveQueue
{
static dispatch_queue_t coredata_background_save_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
coredata_background_save_queue = dispatch_queue_create("com.shashank.coredata.backgroundsaves", NULL);
});
return coredata_background_save_queue;
}
+ (void)performInTheBackground:(void (^)(NSManagedObjectContext *blockContext))bgBlock {
dispatch_async([CGSharedCoreData backgroundSaveQueue],
^{
NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] init];
[newContext setPersistentStoreCoordinator:CG_CORE_DATA.persistentStoreCoordinator]; // Create a managed object context
[newContext setStalenessInterval:0.0];
[newContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
bgBlock(newContext);
});
}
- (void)saveContext {
[CGSharedCoreData saveContext:managedObjectContext_];
}
- (void)clearStore {
NSError *error = nil;
if (self.persistentStoreCoordinator) {
if ([persistentStoreCoordinator_ persistentStores] == nil) {
CGLog(#"No persistent stores to clear!");
}
else {
CGLog(#"Cleaning persistent stores!");
managedObjectContext_ = nil;
NSPersistentStore *store = [[persistentStoreCoordinator_ persistentStores] lastObject];
if (![persistentStoreCoordinator_ removePersistentStore:store error:&error]) {
CGLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
// Delete file
if ([[NSFileManager defaultManager] fileExistsAtPath:store.URL.path]) {
if (![[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error]) {
CGLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
// Delete the reference to non-existing store
persistentStoreCoordinator_ = nil;
}
}
}
#pragma mark - Core Data stack
// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
- (NSManagedObjectContext *)managedObjectContext
{
NSAssert([NSThread isMainThread], #"Must be instantiated on main thread.");
if (managedObjectContext_ != nil) {
return managedObjectContext_;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
managedObjectContext_ = [[NSManagedObjectContext alloc] init];
[managedObjectContext_ setPersistentStoreCoordinator:coordinator];
[managedObjectContext_ setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
}
return managedObjectContext_;
}
// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel
{
if (managedObjectModel_ != nil) {
return managedObjectModel_;
}
managedObjectModel_ = [NSManagedObjectModel mergedModelFromBundles:nil];
return managedObjectModel_;
}
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
CGLog(#"Creating a persistent store!");
NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationLibraryDirectory] stringByAppendingPathComponent: #"CGChatData.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
CGLog(#"Unresolved error %#, %#", error, [error userInfo]);
// abort();
CGLog(#"Delete STORE: %d",[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]);
persistentStoreCoordinator_ = nil;
persistentStoreCoordinator_ = self.persistentStoreCoordinator;
}
return persistentStoreCoordinator_;
}
#pragma mark -
#pragma mark Handling Multiple Contexts
/**
Merges the changes from the insert contexts to the main context on the main thread.
*/
- (void)mergeChanges:(NSNotification *)notification
{
// Merge changes into the main context on the main thread
if(![[CGLoginEngine sharedLoginEngine] isLoggedIn])
return;
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:#selector(mergeChanges:)
withObject:notification waitUntilDone:YES];
return;
}
//CAUTION: Without the for clause below the NSFetchedResultsController may not capture all the changed objects.
//For more info see: http://stackoverflow.com/questions/3923826/nsfetchedresultscontroller-with-predicate-ignores-changes-merged-from-different
//and http://stackoverflow.com/questions/2590190/appending-data-to-nsfetchedresultscontroller-during-find-or-create-loop
for (NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey])
{
[[managedObjectContext_ objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[managedObjectContext_ mergeChangesFromContextDidSaveNotification:notification];
}
now , whenever i need to send a message , I am writing it to core data and updating the tableview with a fetched results controller. The writing part looks like this :
[CGSharedCoreData performInTheBackground:^(NSManagedObjectContext *blockContext)
{
CGLog(#"thread ******* is main %d",[NSThread isMainThread]);
[[CGChatsModelController sharedChatModel] addChatWithtext:[chatMessage objectForKey:#"Message"]
username:[chatMessage objectForKey:#"Receiver"]
firstName:[chatMessage objectForKey:#"ReceiverFirstName"]
lastName:[chatMessage objectForKey:#"ReceiverLastName"]
imageId:[chatMessage objectForKey:#"ReceiverImageID"]
createdByMe:YES
time:time
context:blockContext];
[CGSharedCoreData saveContext:blockContext];
But , When I send multiple messages in a very short span , it entirely blocks my UI, even when the core-data saving and all other related operations are being done on a background queue.
Is there any particular reason for why ,this is happening ?
I am attaching a few other blocks of my code for reference here :
- (CGChat *) addChatWithtext:(NSString *)text username:(NSString *)username firstName:(NSString *)firstName lastName:(NSString *)lastName imageId:(NSString *)imageId createdByMe:(BOOL)yesOrNo time:(NSDate *)date context:(NSManagedObjectContext *)context
{
NSManagedObjectContext *backgroundContext = context;
CGChat *chat = (CGChat *)[NSEntityDescription insertNewObjectForEntityForName:#"CGChat" inManagedObjectContext:backgroundContext];
chat.text = text;
chat.createdByMe = [NSNumber numberWithBool:yesOrNo];
chat.status = #"sent";
[self addChat:chat toUserWithUserName:username firstName:firstName lastName:lastName imageID:imageId time:date WithContext:backgroundContext];
return chat;
}
- (CGChat *)lookUpChatWithUserName:(NSString *)username text:(NSString *)text timeStamp:(NSString *)timeStamp createdByMe:(BOOL) yesOrNo context:(NSManagedObjectContext *)context
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CGChat" inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"userIchatWith == %# && text == %# && timeStamp == %# && createdByMe ==%#", [self lookUpForUserWithUsername:username inContext:context],text,timeStamp,[NSNumber numberWithBool:yesOrNo]];
[request setPredicate:predicate];
NSArray *resultArray = nil;
NSError *error;
resultArray = [context executeFetchRequest:request error:&error];
CGChat *chat = nil;
if ([resultArray count] > 0) {
chat = [resultArray objectAtIndex:0];
}
return chat;
}
- (void) addChat:(CGChat *)chat toUserWithUserName:(NSString *)username firstName:(NSString *)firstName lastName:(NSString *)lastName imageID:(NSString *)imageId time:(NSDate *)date WithContext:(NSManagedObjectContext *)context
{
CGUser *user = [self lookUpForUserWithUsername:username inContext:context];
if (!user)
{
user = (CGUser *)[NSEntityDescription insertNewObjectForEntityForName:#"CGUser" inManagedObjectContext:context];
user.userName = username ;
}
user.firstName = firstName;
user.lastName = lastName;
if (![user.imageID isEqualToString:imageId])
{
user.imageID = imageId;
}
CGChat *chats = [self getLastChatForUsername:username andContext:context];
if(chats)
{
chats.isLastChat = [NSNumber numberWithBool:NO];
}
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
chat.timeStamp = [dateFormat stringFromDate:date];
chat.isLastChat = [NSNumber numberWithBool:YES];
chat.userIchatWith = user;
user.timeOfLatestChat = [dateFormat stringFromDate:date];
}
- (CGChat *) getLastChatForUsername:(NSString *)username andContext:(NSManagedObjectContext *)context
{
CGUser *user = [self lookUpForUserWithUsername:username inContext:context];
if(user)
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"isLastChat == %#",[NSNumber numberWithBool:YES]];
NSSet *filteredSet = [user.chats filteredSetUsingPredicate:predicate];
return [[filteredSet allObjects] lastObject];
}
else
{
return nil;
}
}
- (CGUser *) lookUpForUserWithUsername:(NSString *)username inContext:(NSManagedObjectContext *)context
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CGUser" inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"userName == %#", username];
[request setPredicate:predicate];
NSArray *resultArray = nil;
NSError *error;
resultArray = [context executeFetchRequest:request error:&error];
CGUser *user = nil;
if ([resultArray count] > 0) {
user = [resultArray objectAtIndex:0];
}
return user;
}
There are several issues here:
Core Data does not throw NSException, do not wrap your Core Data calls in try/catch. That will just create noise if something else does throw an exception. try/catch is VERY rarely used in Objective-C programming.
You are listening for and merging changes from NSManagedObjectContextDidSaveNotification calls when you are using a parent/child context design. This is incorrect. The parent/child relationship handles this for you, automatically, whenever you save the child context. When you listen for and consume that notification you are forcing Core Data to process that information a second time, on the main thread.
It is not clear what "background context" you are passing around as you do not show the code that calls your -add... methods. Background contexts should not persist for long periods of time. They are really meant to be used and destroyed. The longer you have a background context in existence the further from the main context it is going to be as changes in the main context do NOT get passed down to the child contexts.
Update
My apologies, I misread the code. Since you are merging the changes from a background thread into the main thread, there is no way to avoid blocking the main thread. This is one of the primary reasons for the creation of the parent/child design.
The only way to avoid this, and it is not recommended would be to:
Stand up a new NSPersistentStoreCoordinator pointed at the same sqlite file
Update that NSPersistentStoreCoordinator from the background thread
Notify the main NSManagedObjectContext to reload all data
This will avoid most of the main thread blocking but the expense in code complexity is almost always too much.

Resources