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];
}];
}
}];
}
Related
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.
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);
}
I'm new to NSManagedObjectContext. I have created an entity Link in my app, which contains a NSString *url.
At some point of my app, I need to insert a new Link in my base, so I simply do this :
Link *link = [NSEntityDescription
insertNewObjectForEntityForName:#"Link"
inManagedObjectContext:self.managedObjectContext];
link.url = myUrl;
But before doing this, I want to check if there is not already a Link in my base with the same url. And I have no idea of how to do so... what should I do?
EDIT : to retrieve data from the base I'm using this method from a tool I found on the web:
// Fetch objects with a predicate
+(NSMutableArray *)searchObjectsForEntity:(NSString*)entityName withPredicate:(NSPredicate *)predicate andSortKey:(NSString*)sortKey andSortAscending:(BOOL)sortAscending andContext:(NSManagedObjectContext *)managedObjectContext
{
// Create fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
// If a predicate was specified then use it in the request
if (predicate != nil)
[request setPredicate:predicate];
// If a sort key was passed then use it in the request
if (sortKey != nil) {
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:sortAscending];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
}
// Execute the fetch request
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
// If the returned array was nil then there was an error
if (mutableFetchResults == nil)
NSLog(#"Couldn't get objects for entity %#", entityName);
// Return the results
return mutableFetchResults;
}
I would like to know how to use it.
Thanks for your help.
The method you provided just searches for a NSManagedObject that matches the attributes in the NSManagedObjectContext and if one exists, it returns it.
But, what you need to implement is called the Find-or-Create pattern, which is discussed in the Core Data programming guide. Basically, you search for an object matching specific criteria and if it exists that object is returned. If that object does not exist you create it.
Core Data Programming Guide
E.g.
+ (NSString *)entityName
{
return NSStringFromClass([Link class]);
}
+ (instancetype)findUsingPredicate:(NSPredicate *)predicate withContext:(NSManagedObjectContext *)context
{
Link * entity;
// Setup the fetchRequest
NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:[[self class] entityName]];
fetchRequest.predicate = predicate;
// Credit: #Martin R
[fetchRequest setFetchLimit:1];
// Execute the fetchRequest
NSError *error = nil;
NSArray * matchingLinks = [context executeFetchRequest:fetchRequest error:&error];
// MatchingLinks will only return nil if an error has occurred otherwise it will return 0
if (!matchingLinks)
{
// handle error
// Core data returns nil if an error occured
NSLog(#"%s %#", __PRETTY_FUNCTION__, [error description]);
}
else if ([matchingLinks count] <= 0)
{
// if the count <= 0, there were no matches
NSLog(#"%s Not found", __PRETTY_FUNCTION__);
} else {
// A link with a url that matches the url in the dictionary was found.
NSLog(#"%s Found", __PRETTY_FUNCTION__);
entity = [matchingLinks lastObject];
}
return entity;
}
+ (instancetype)findUsingPredicate:(NSPredicate *)predicate orCreateWithContext:(NSManagedObjectContext *)context
{
Link * entity = [[self class] findUsingPredicate:predicate withContext:context];
if (!entity) {
entity = [[self class] createWithContext:context];
}
return entity;
}
+ (isntancetype)createWithContext:(NSManagedObjectContext *)context
{
return [[self class] alloc] initWithContext:context];
}
- (instancetype)initWithContext:(NSManagedObjectContext *)context
{
Link * entity = [NSEntityDescription insertNewObjectForEntityForName:[[self class] entityName] inManagedObjectContext:context];
return entity;
}
USE CASE:
NSString * url = #"http://www.mylink.com";
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"url = %#", url];
Link * link = [Link findUsingPredicate:predicate orCreateWithContext:self.managedObjectContext];
link.url = url;
You can do it like this (with your method):
AppDelegate *del = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = del.managedObjectContext;
NSString *urlString = #"YOUR URL HERE";
NSMutableArray *fetchedResults = [self searchObjectsForEntity:#"Link" withPredicate:[NSPredicate predicateWithFormat:#"url == %#", urlString] andSortKey:#"url" andSortAscending:YES andContext:managedObjectContext];
BOOL exist = NO;
if(fetchedResults.count >= 1){
exist = YES;
}
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.
I currently have a problem deleting an object in a to many relationship.
My app have the following relationship:
Product <<- Cart
When the user pushes a "add to cart" button in my viewcontroller, the following code is setting the relations between the product object and the cart
+ (Cart *)addProductToCartWithProduct:(Product *)product inManagedObjectContext:(NSManagedObjectContext *)context {
Cart *cart = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Cart"];
NSError *error = nil;
NSArray *carts = [context executeFetchRequest:request error:&error];
if (!carts || ([carts count] > 1)) {
// handle error
} else if (![carts count]) {
cart = [NSEntityDescription insertNewObjectForEntityForName:#"Cart" inManagedObjectContext:context];
} else { // they already have a cart started
cart = [carts lastObject];
}
/*Get Object ID to safely pass NSMangedObject between threads (A background worker thread and the main thread). */
NSManagedObjectID *retID = [product objectID];
[cart addProductsObject:(Product *)[context objectWithID:retID]];
//Inverse relationship
[(Product *) [context objectWithID:retID] setInCart:cart];
return cart;
}
This then returns a cart object, which I pass to my cart viewcontroller, and fetch the products in that relationship like so:
// Fetch request for "Product":
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Product"];
// Fetch only products for the cart:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"inCart = %#", self.cart];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"navn" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:_theManagedObjectContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
When I then try to delete an object from the relationship like so:
-(void)RemoveFromCart:(UIButton *)sender {
NSIndexPath *ip = [NSIndexPath indexPathForRow:sender.tag inSection:0];
Product *prod = (Product *)[self.fetchedResultsController objectAtIndexPath:ip];
prod.inCart = nil;
[_cart removeProductsObject:prod];
NSLog(#"Cart %# %#", _cart.products, prod);
[self saveCurrentContext:_theManagedObjectContext];
[self loadCart];
[_orderTable reloadData];
}
The product is removed visually (gone from the tableview/screen) because inCart is set to nil, but not technically... when I log the cart object, the product object is still in the relationship, so it seems like the [_cart removeProductsObject:prod]; is not working.
And it also doesn't work the other way around, when I try to add the same product to the cart, I just have deleted (from the cart), for some reason the inverse relationship "inCart" is not set, after I have set it to "nil", when the product object is removed.
Why is this happening? and how do I fix it? :).
EDIT:
Pictures showcasing inverse relationships in Core data model editor:
Pass Cart to other viewcontroller:
[[[DataManager sharedInstance] backgroundManagedObjectContext] performBlock:^{
UITabBarController *tabBarController = self.tabBarController;
for (UINavigationController *navController in tabBarController.viewControllers) {
for (UIViewController *vc in navController.viewControllers) {
if ([vc isMemberOfClass:NSClassFromString(#"CartViewController")]){
CartViewController *cartVC = (CartViewController *) vc;
cartVC.cart = [Cart addProductToCartWithProduct:prod inManagedObjectContext: [[DataManager sharedInstance] backgroundManagedObjectContext]];
[[DataManager sharedInstance] saveBackgroundContext];
[[DataManager sharedInstance] saveMasterContext];
NSLog(#" %#", cartVC.cart);
}
}
}
}];
Save Context
-(void)saveCurrentContext:(NSManagedObjectContext *)context {
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"NOT SAVED");
}
[[DataManager sharedInstance] saveBackgroundContext];
[[DataManager sharedInstance] saveMasterContext];
}
Okay, so I found a work around for this very weird problem.
Since the autogenerated accessor methods didn't work for some reason, I had to think of another way to delete the object from the relationship.
for (Product *prod in _cart.products) {
//To Very reliable to check for item by name attribute, but It works :)
if ([prod.name isEqualToString:product.name]) {
product = prod;
NSMutableSet *set = [NSMutableSet setWithSet:_cart.products];
[set removeObject:prod];
_cart.products = set;
}
}