I am trying to update NSManagedObjects using magical record by fetching all objects and then using for loop changing every object and after saving context no change is there
NSArray * locationArray = [[NSArray alloc]initWithArray:[Locations findAllInContext:[NSManagedObjectContext contextForCurrentThread]]];
for(Locations * currentLocation in locationArray)
{
currentLocation.name = #"Hello world";
}
[[NSManagedObjectContext contextForCurrentThread]saveToPersistentStoreAndWait];
You can simply place you code in to the MagicalRecord's saving block
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// This block runs in background thread
NSArray * locationArray = [[NSArray alloc]initWithArray:[Locations findAllInContext:[NSManagedObjectContext contextForCurrentThread]]];
for(Locations * currentLocation in locationArray){
currentLocation.name = #"Hello world";
}
} completion:^(BOOL success, NSError *error) {
// This block runs in main thread
}];
Related
This is database design with to-many relations.This is my method
-(void)fetchChannelData:(id)responseObject{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *channels = [responseObject objectForKey:#"channels"] ;
for (NSDictionary *channelStack in channels) {
NSLog(#"Tenant id %#",[[ NSUserDefaults standardUserDefaults ] valueForKey:#"test"]);
ChannelsData *channels = [ChannelsData MR_importFromObject:channelStack inContext:localContext];
[channels setCategory:[[channelStack valueForKey:#"category"] firstObject]];
[channels setTenantID:[[ NSUserDefaults standardUserDefaults ] valueForKey:#"test"]];
NSArray *channelLogo = [channelStack objectForKey:#"channelImages"] ;
for (NSDictionary *channelLogoStack in channelLogo) {
ChannelImages *images = [ChannelImages MR_importFromObject:channelLogoStack inContext:localContext];
[channels addImagesObject:images];
[images setChannelID:channels.channelID];
}
}
} completion:^(BOOL contextDidSave, NSError *error) {
if (contextDidSave) {
[self fetchListing];
}
}];
}
Where I am importing my data. I have dropdown where I selected different API points for specific data. All fetching different data.Now issue is that when I launch my app again service is again called and instead data is updated it insert it again.I already Used relatedByAttribute on each entity letting their "ID" be the primary key.]1]1
Problem: Fetching a managed object using a background thread does not lazy load the NSManaged object relationship correctly when the NSManaged object that is related has a custom setter. Doing fetch on main thread with main concurrency type works without a problem. Why is this?
Work Around: If I create a custom getter on the relationship object and check for nil, I can force the NSManaged object to load by calling other variables that don't have custom setter methods.
Background
The core data layout is pretty simple. I have a Game managed object and a Turn managed object. The turn object is a one to one relationship with the game object. I always fetch the game object in order to access the turn object. TurnImp and GameImp are implementation classes that inherit from the Game and Turn object so I don't put getter/setter methods in auto generated code.
Code
The Fetch
//
//Stick command on background
//
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
//
//Load Game
//
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
CoreDataHelper *coreDataHelper = appDelegate.coreDataHelper;
NSManagedObjectContext *childMOC = [coreDataHelper createChildManagedObjectContext];
//the request
NSFetchRequest *fetchRequest = [NSFetchRequest new];
//the object entity we want
NSEntityDescription *entity = [NSEntityDescription entityForName:GAMEIMP_GAME inManagedObjectContext:childMOC];
[fetchRequest setEntity:entity];
//the predicate rules, the what
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"gameId == %#", #"1404110671234567"];
[fetchRequest setPredicate:predicate];
//the sorting rules
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:GAMEIMP_OBJECT_ID ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
//Fetch results
NSFetchedResultsController *resultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:childMOC sectionNameKeyPath:nil cacheName:nil];
NSError *error;
BOOL success = [resultsController performFetch:&error];
GameImp *game;
if (success) {
game = [resultsController.fetchedObjects objectAtIndex:0];
} else {
NSLog(#"Unable to get game. Error: %#", error);
}
TurnImp *turnImp = game.turn;
//Issue is here!!! Should be 3, instead 0 because lastRoundReward is nil.
int lastRoundReward = [turnImp.lastRoundReward intValue];
//Work around, call custom getter method. Now 3 is returned.
lastRoundReward = [turnImp getLastRoundReward];
}
This childMOC creation
-(NSManagedObjectContext*) createChildManagedObjectContext {
NSManagedObjectContext *childMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
childMOC.parentContext = self.mainManagedObjectContext;
return childMOC;
}
TurnImp Header
#interface TurnImp : Turn
#property(atomic) BOOL isValid;
- (void) setLastRoundReward: (int) lastRoundReward;
- (int) getLastRoundReward;
#end
TurnImp M
#implementation TurnImp
#synthesize isValid;
#synthesize lastRoundReward = _lastRoundReward;
/**
* Set the last round reward
* #param -
* #return -
*/
- (void) setLastRoundReward: (int) lastRoundReward {
_lastRoundReward = [NSNumber numberWithInt:lastRoundReward];
}
/**
* Get the int value of lastRoundReward
*/
- (int) getLastRoundReward {
//Note - HACK! Lazy loading not working, try another member
if (self.lastRoundReward == nil) {
//Force load
NSString *objectId = self.objectId;
}
return [self.lastRoundReward intValue];
}
Change childMoc to mainMoc and it works. MainMoc Code
//create the main MOC
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
More After Fixed Concurrency issue
[childMOC performBlock:^{
// Execute the fetch on the childMOC and do your other work.
NSError *error;
NSArray *results = [childMOC executeFetchRequest:fetchRequest error:&error];
if (results == nil) {
// Handle error
} else if (results.count == 1) {
GameImp *game = [results firstObject];
TurnImp *turnImp = game.turn;
//Issue is here!!! Should be 3, instead 0 because lastRoundReward is nil.
int lastRoundReward = [turnImp.lastRoundReward intValue];
//Work around, call variable objectId (not same as ObjectId)
NSString *objectId = turnImp.objectId;
//not it's 3...
lastRoundReward = [turnImp.lastRoundReward intValue];
}
}];
Work Around
I removed the following from TurnImp and it works as expected with the relationships.
#synthesize lastRoundReward = _lastRoundReward;
First, I have to confess that I have no idea what your problem statement means - what is lazy loading of a relationship supposed to do anyway?
However, a quick glance at your code reveals that you are creating a MOC with NSPrivateQueueConcurrencyType yet you are not properly wrapping its use inside an appropriate performBlock invocation.
When you clearly violate the Core Data Concurrency guidelines, you are playing in dangerous waters and will get undefined behavior.
Also, why create an instance of NSFetchedResultsController just to perform a fetch? That's overkill. Simply use a fetch request. Like so...
[childMOC performBlock:^{
// Execute the fetch on the childMOC and do your other work.
NSError *error;
NSArray *results = [childMOC executeFetchRequest:fetchRequest error:&error];
if (result == nil) {
// Handle error
} else if (results.count == 1) {
GameImp *game = [results firstObject];
TurnImp *turnImp = game.turn;
int lastRoundReward = [turn2.lastRoundReward intValue];
}
}];
I'm using MagicalRecord to change my custom CoreData functions with something better.
I have two entities: Offer (videogame offers) and System (consoles, PC, etx). An Offer can have one or many Systems, and I get all the data from a Parse query, where I save all my data.
I only have 8 Systems so when my app starts, I fetch all of them and save with Magical Record. Then I call my method to fetch some offers and transform the PFObjects from Parse into entities.
This is how
+ (void)createOrUpdateOffersWithArray:(NSArray *)objects completion:(void (^)())completion
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
for (PFObject *object in objects) {
Offer *offer = [Offer MR_findFirstByAttribute:#"offerId" withValue:object.objectId inContext:localContext];
if (offer == nil) {
offer = [Offer MR_createEntityInContext:localContext];
}
// Here I set all the offer values (title, date, etc)...
NSSet *offerSavedSystems = [offer mutableSetValueForKey:#"systems"];
if (offerSavedSystems == nil) {
offerSavedSystems = [[NSMutableSet alloc] init];
}
/* This is a set of all the systems related with the offer */
NSArray *offerSystems = [object objectForKey:#"sistemas"];
NSMutableSet *updatedSystems = [[NSMutableSet alloc] init];
/* Here I query one of my System entities for each in the "offerSystems" array */
for (NSString *systemKey in offerSystems) {
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey inContext:localContext];
[updatedSystems addObject:system];
}
offer.systems = updatedSystems;
}
} completion:^(BOOL contextDidSave, NSError *error) {
/* UI update*/
}];
}
The weird this happens inside the last for loop. Despite I'm sure that all the 8 systems are inside my CoreData model, this line
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey inContext:localContext];
returns nil
But the most weird thing is that using NSLOG just before the for loop like this
NSLog(#"SYSTEMS %d", [System MR_countOfEntities]);
NSLog(#"SYSTEMS WITH LOCAL CONTEXT %d", [System MR_countOfEntitiesWithContext:localContext]);
I got this
SYSTEMS 8
SYSTEMS WITH LOCAL CONTEXT 0
My Systems are previously saved with this method
+ (void)initializeSystems
{
NSArray *systemsKeys = [[self systems] allKeys];
for (NSString *systemKey in systemsKeys) {
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey];
if (system == nil) {
system = [System MR_createEntity];
}
[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {
system.key = systemKey;
system.name = [self literalForSystem:systemKey];
system.subscribed = [NSNumber numberWithBool:NO];
}];
}
}
What I'm doing wrong?
Thanks for your time
There are several possible problems with this line of code
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey inContext:localContext];
systemKey value does not exist inside all of your entities.
system.key value not setted (or nil)
So, check firstly - fetch all system entities and log 'key' value. Insure that your key really exist.
Secondly,it's better to refactor your code for background saving
+ (void)initializeSystemsWithCompletion:(void (^)())completion
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *systemsKeys = [[self systems] allKeys];
for (NSString *systemKey in systemsKeys) {
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey inContext:localContext];
if (system == nil) {
system = [System MR_createEntityInContext:localContext];
}
system.key = systemKey;
system.name = [self literalForSystem:systemKey];
system.subscribed = [NSNumber numberWithBool:NO];
}
} completion:^(BOOL success, NSError *error) {
}];
}
I am using Magical Record for my app so I can make use of their core data stack that should automatically propagate my changes in my worker context (core data thread) to the default context (of the main thread). I have been creating and updating my objects in a core data write queue and everything was working fine.
Then I ran into this issue that Magical Record was able to save my changes on my worker context, but when it tries to save to the default context, it detects no changes and therefore doesn't save.
Where did I do wrong? In the rest of my app, I am creating and updating in pretty much the same way and it works. Please help. Thank you!
Below is the related code:
Where no changes were detected after all these changes:
dispatch_async(CoreDataWriteQueue(), ^{
if (self.person) {
FTPerson *localPerson = [FTPerson fetchWithID:self.person.id];
[localPerson setName:self.nameField.text];
[localPerson trainWithImages:self.addedImages];
} else {
FTPerson *newPerson = [[FTPerson alloc] initWithName:self.nameField.text andInitialTrainingImages:self.addedImages];
FTGroup *localGroup = [FTGroup fetchWithID:self.group.id];
[newPerson addGroup:localGroup];
}
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait];
});
I have also tried the saveWithBlock method and no luck:
dispatch_async(CoreDataWriteQueue(), ^{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
if (self.person) {
FTPerson *localPerson = [FTPerson fetchWithID:self.person.id];
[localPerson setName:self.nameField.text];
[localPerson trainWithImages:self.addedImages];
} else {
FTPerson *newPerson = [[FTPerson alloc] initWithName:self.nameField.text andInitialTrainingImages:self.addedImages];
FTGroup *localGroup = [FTGroup fetchWithID:self.group.id];
[newPerson addGroup:localGroup];
}
}];
});
And here is where I created the person and group objects:
dispatch_async(CoreDataWriteQueue(), ^{
FTPerson *newPerson = [[FTPerson alloc] initWithName:#"test" andInitialTrainingImages:#[[UIImage imageNamed:#"test.jpg"]]];
});
dispatch_async(CoreDataWriteQueue(), ^{
FTGroup *newGroup = [[FTGroup alloc] init];
[self setGroup:newGroup];
});
Also the init methods:
Person
- (id)initWithName:(NSString *)name andInitialTrainingImages:(NSArray *)images {
NSManagedObjectContext *context = [NSManagedObjectContext MR_contextForCurrentThread];
self = [FTPerson MR_createInContext:context];
self.name = name;
self.id = [[NSUUID UUID] UUIDString];
self.objectIDString = [[self.objectID URIRepresentation] absoluteString];
self.trainingImages = [[NSMutableArray alloc] init];
[context MR_saveToPersistentStoreAndWait];
return self;
}
Group
- (id)init {
NSManagedObjectContext *context = [NSManagedObjectContext MR_contextForCurrentThread];
self = [FTGroup MR_createInContext:context];
self.id = [[NSUUID UUID] UUIDString];
self.didFinishTraining = NO;
self.didFinishProcessing = NO;
self.photosTrained = #(0);
self.lastProcessedDate = [NSDate date];
[context MR_saveToPersistentStoreAndWait];
return self;
}
FIXED
So the problem ended up being unrelated to magical record.
I was adding objects to my core data NSSet incorrectly. Instead of making it a NSMutableSet and do addObject:, I should have:
NSMutableSet *mutablePhotos = [self mutableSetValueForKey:#"photos"];
[mutablePhotos addObject:photo];
So the problem ended up being unrelated to magical record. I was adding objects to my core data NSSet incorrectly. Instead of making it a NSMutableSet and do addObject:, I should have:
NSMutableSet *mutablePhotos = [self mutableSetValueForKey:#"photos"];
[mutablePhotos addObject:photo];
I don't think you should be using your own dispatch queues when dealing with Core Data. The whole point of the -performBlock: methods on NSManagedObjectContext is that Core Data takes care of executing the provided block on the correct queue.
Now to answer your question. First, are you using MagicalRecord 2.x or 3.0? If 2.x, please make sure you use the develop branch, or alternatively grab the latest release (v2.3beta5 at this time). There are a lot of improvements in the latest dev and release branches.
Second, I think it is important when multithreading with Core Data to do two things:
Always turn on concurrency debugging by editing your scheme and setting -com.apple.CoreData.ConcurrencyDebug 1
Always be very explicit as to which context you mean to use. There is a reason that the method -MR_contextForCurrentThread is no longer recommended by the MagicalRecord team. Modify your create methods to take an NSManagedObjectContext as a parameter so there is never a doubt as to what context is creating it.
Try making the following changes:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
if (self.person) {
FTPerson *localPerson = [self.person MR_inContext:localContext];
[localPerson setName:self.nameField.text];
[localPerson trainWithImages:self.addedImages];
} else {
FTPerson *newPerson = [[FTPerson alloc] initWithName:self.nameField.text andInitialTrainingImages:self.addedImages inContext:localContext];
FTGroup *localGroup = [self.group MR_inContext:localContext];
[newPerson addGroup:localGroup];
}
}];
Person:
- (id)initWithName:(NSString *)name andInitialTrainingImages:(NSArray *)images inContext:(NSManagedObjectContext*)context {
self = [FTPerson MR_createInContext:context];
self.name = name;
self.id = [[NSUUID UUID] UUIDString];
self.objectIDString = [[self.objectID URIRepresentation] absoluteString];
self.trainingImages = [[NSMutableArray alloc] init];
[context MR_saveToPersistentStoreAndWait];
return self;
}
For Person, I notice you pass in an array of images but you assign an empty array to the new person entity. Do you mean to do that?
Group:
-(id)initWithContext:(NSManagedObjectContext*)context {
self = [FTGroup MR_createInContext:context];
self.id = [[NSUUID UUID] UUIDString];
self.didFinishTraining = NO;
self.didFinishProcessing = NO;
self.photosTrained = #(0);
self.lastProcessedDate = [NSDate date];
[context MR_saveToPersistentStoreAndWait];
return self;
}
I'm using GCD as well as Core Data. I'm aware Core Data is not thread safe, so I create child Managed Object Context (tempContext) with the mainContext as the parent. MainContext has a PCS to save data. What I'm doing is:
Pull posts from Facebook
Save each post to Core Data by creating a new Managed Object called Post, updating it and saving it
while I process each post, if it needs additional handling, I add the post.obectID to an NSMutableArray of Managed Object IDS. This NSMutableArray is used by another process to finish updating the posts. I am using object IDs because the separate process will not be in the same tempContext, thus I will fetch the Post from Core Data using the object ID.
The process gets data, and I see it is stored within store.data (I use the product called Base to view the physical database file's content). But, I cannot seem to store the post's Object ID. As you see in code, I am using NSLog to print out the post's object ID, and I see them, thus I know the object IDs are there. Code also shows I am doing [tempContext save] and I am doing [mainContext save:], thus I should now have permanent, not temporary object IDs for posts. Again, the data is within Core data's physical file, but the mutable array count is still = 0.
What am I doing wrong to save the object IDs to NSMutableArray? In code you will see commented out where I've tried running the [mutableArray addObject:p.objectID] even on the main Queue, but it does not matter.
Here is my code:
- (void)processPosts {
NSLog(#"-- Begin processPosts --");
dispatch_async(_processPostsQ, ^{
for (NSDictionary * info in _posts)
{
NSManagedObjectContext * tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
tempContext.parentContext = mainMOContext;
// If Post exists, rewrite over top of it. otherwise create a new one
NSFetchRequest * fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription * entity = [[mainMOModel entitiesByName] objectForKey:#"Post"];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"fb_post_id = %#",
[info objectForKey:#"post_id"]];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey:#"fb_post_id" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sd]];
NSError * error = nil;
NSArray * fetchResults = [tempContext executeFetchRequest:fetchRequest error:&error];
Post * p = [NSEntityDescription insertNewObjectForEntityForName:#"Post" inManagedObjectContext:tempContext];
if (fetchResults.count > 0)
{
p = [fetchResults objectAtIndex:0];
}
NSDictionary * appData = [[NSDictionary alloc]init];
appData = [info objectForKey:#"app_data"];
p.fb_actor_id = [info objectForKey:#"actor_id"];
p.fb_app_data = [self archivedData:[info objectForKey:#"app_data"]];
p.fb_attachment = [self archivedData:[info objectForKey:#"attachment"]];
p.fb_created_time = [info objectForKey:#"created_time"];
p.timestamp = [info objectForKey:#"updated_time"];
p.fb_attribution = NULL_TO_NIL([info objectForKey:#"attribution"]);
p.fb_message = NULL_TO_NIL([info objectForKey:#"message"]);
p.fb_type = NULL_TO_NIL([info objectForKey:#"type"]);
p.fb_post_id = [info objectForKey:#"post_id"];
p.fb_likes_info = [self archivedData:[info objectForKey:#"like_info"]];
p.fb_comments_info = [self archivedData:[info objectForKey:#"comment_info"]];
p.fb_parent_post_id = NULL_TO_NIL([info objectForKey:#"parent_post_id"]);
p.fb_permalink = NULL_TO_NIL([info objectForKey:#"permalink"]);
p.fb_photo_data = nil;
p.fb_place = NULL_TO_NIL([info objectForKey:#"places"]);
p.fb_source_id = [info objectForKey:#"source_id"];
p.social_network = #"facebook";
p.fkPostToFriend = [[FriendStore sharedStore]findFriendWithFBUID:[info objectForKey:#"source_id"] withMOContext:tempContext];
[tempContext save:&error];
dispatch_async(dispatch_get_main_queue(), ^{
NSError * error;
[mainMOContext save:&error];
});
if (appData.count > 0)
{
if ([[appData objectForKey:#"photo_ids"] count] > 0)
{
NSLog(#" -- photos need to be loaded for postID: %#",p.objectID);
//dispatch_async(dispatch_get_main_queue(), ^{
[_postsNeedingPhotos addObject:p.objectID];
//});
}
}
}
NSLog(#"ProcessPosts: num of posts needing photos: %d",_postsNeedingPhotos.count);
dispatch_async(_getPhotosQ, ^{
[self loadPhotoData];
});
NSLog(#"-- End processPosts --");
});
}
Thanks in advance for your help!
I'm an idiot. I forgot to initialize _postsNeedingPhotos with:
_postsNeedingPhotos = [[NSMutableArray alloc] init];
Now everything runs fine... :)