I have this piece of code inside my viewDidLoad method to check the name of the section from the UITableView. What I need is a way to make a similar loop inside the same method to be able to know an attribute value from all stored objects. The Core Data entity is defined in a NSManagedObject subclass called ToDoItem.
for(int i = 0; i < [[self.fetchedResultsController sections] count]; i++)
{
id <NSFetchedResultsSectionInfo> theSection = [[self.fetchedResultsController sections]objectAtIndex:i];
NSString *sectionName = [theSection name];
if ([sectionName isEqualToString:#"0"])
{
haySeccion0 = #"si";
}
}
My suggestion for you is:
fetch only what you need:
NSManagedObjectContext* context = [[NSManagedObjectContext alloc] init] //or use your current context;
context.persistentStoreCoordinator = //Get the coordinator
NSExpressionDescription* objectIdDesc = [NSExpressionDescription new];
objectIdDesc.name = #"objectID";
objectIdDesc.expression = [NSExpression expressionForEvaluatedObject];
objectIdDesc.expressionResultType = NSObjectIDAttributeType;
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:#"EntityName"];
[r setResultType:NSDictionaryResultType];
[r setPropertiesToFetch:#[objectIdDesc,#"borrar"]];
[r setPredicate:[NSPredicate predicateWithFormat:#"has0Section = %#",#"si"]];
NSError* error = nil;
NSArray* results = [context executeFetchRequest:r error:&error];
//Error handling ...
results will now contain all objects (as dictionaries) on section 0 and their borrar property, perform whatever logic you like on that.
You can perform this code in the background and set the map to some private variable of your view controller (synchronise on some lock when needed).
It sounds like you want to check all the objects then perform an action on a section based on an attribute being on an object.
Why not enumerate all objects, then use indexPathForObject to retrieve the section.
It isn't clear why you need to do this section by section..
Related
I perform a fetch from core data of nsmanaged objects. One of the attributes of the managed objects is an NSNumber, corresponding to the id of the objects (not Apple's objectid, my own sequential NSNumber id scheme). For a given object which has an id#, I would like to get the index of that object in the array. However, I can't figure out a way to get a list of the id#s as valueforkey does not work for nsnumbers and objectforkey throws an error with an nsmutablearray. Can anyone suggest how to do this?
Here is my non-working code. The problem is that I can't get a valid list of ids out of the fetched objects.
//FETCH ITEMS
- (id) getItems{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Items"];
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"lasttouched" ascending:NO];
[fetchRequest setSortDescriptors:#[sort]];
NSError *error = nil;
self.managedObjectContext = [IDModel sharedInstance].managedObjectContext;
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest
NSMutableArray *mutableresults = [results mutableCopy];
[mutableresults removeObjectIdenticalTo:[NSNull null]];
NSArray*someids =[[mutableresults valueForKey:#"myid"] mutableCopy];
//THIS IS EMPTY WHEN LOGGED
_someids = someids;//save to ivar
return mutableresults; //an array of activity objects with all their properties
}
// In method to get index of the object
NSInteger myIndex=[_someids indexOfObject:#33];
NSLog(#"anIndex%ld",(long)anIndex);
You need to remap the objects into a new array. For straight forward something like:
NSMutableArray *arrayOfIdentifiers = [[NSMutableArray alloc] init];
for(WhateverMyManagedObjectIs *item in results) { if(item.myid) [arrayOfIdentifiers addObject:item.myid]; }
This will result in array of ids as NSNumber objects only. Since NSNumber is an object the indexOfObject may not work. The reason is that it will compare pointers instead of values. So you will need to use compare method like:
NSInteger index = -1;
for(NSInteger i=0; i<_someids.count; i++) {
if([_someids[i] compare:#33] == NSOrderedSame) {
index = i;
break; // Break after first one is found
}
}
So in the end it is pretty much the same if you skip mapping your objects and simply compare it on the same object array:
NSInteger index = -1;
for(NSInteger i=0; i<results.count; i++) {
if([[results[i] myid] compare:#33] == NSOrderedSame) {
index = i;
break; // Break after first one is found
}
}
In my application, I'm downloading data from web service with pagination. Output is a json array of dictionaries.
Now, I am saving the output json array in core data. So, my problem is, every time calls the saveInCoreData: method with the result array, it creates duplicate objects in the data base. How can i check for an object and update or replace the object if its already exists?
myId is a uniq key.
// save in coredata
+ (void) saveInCoreData:(NSArray *)arr{
// get manageObjectContext
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
if(arr != nil && arr.count > 0) {
for(int i=0; i < arr.count; i++){
SomeEntity *anObj = [NSEntityDescription
insertNewObjectForEntityForName:#"SomeEntity"
inManagedObjectContext:context];
anObj.description = [[arr objectAtIndex:i] objectForKey:#"description"];
anObj.count = [NSNumber numberWithInteger:[[[arr objectAtIndex:i] objectForKey:#"count"] integerValue]];
// Relationship
OtherEntity *anOtherObject = [NSEntityDescription
insertNewObjectForEntityForName:#"OtherEntity"
inManagedObjectContext:context];
creatorDetails.companyName = [[[arrTopics objectAtIndex:i] objectForKey:#"creator"] objectForKey:#"companyName"];
}
}
The most efficient way to avoid duplicates is to fetch all the objects you already have, and avoid processing them when iterating over the results.
Get the topicIds from the results:
NSArray *topicIds = [results valueForKeyPath:#"topicId"];
Fetch existing topics with these topicIds:
NSFetchRequest *request = ...;
request.predicate = [NSPredicate predicateWithFormat:#"%K IN %#",
#"topicId", topicIds];
NSArray *existingTopics = [context executeFetchRequest:request error:NULL];
Get the existing topicIds:
NSArray *existingTopicIds = [existingTopics valueForKeyPath:#"topicId"];
Process the results:
for (NSDictionary *topic in results) {
if ([existingTopicIds containsObject:topic[#"topicId"]]) {
// Update the existing topic if you want, or just skip.
continue;
}
...
}
Attempting to fetch each existing topic individually, within the processing loop, will be very inefficient in terms of time. The tradeoff is more memory usage, but as you are only getting 20 objects at a time, this should be a complete non-issue.
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 have two entities, one called InProject that has several attributes and one relationship. the relationship is with another entity called Ins.
I am editing one of the Ins that is related to InProject. I used InProject attribute ID which then returns a NSDictionary value that has several key-values one of which is for an array of Ins. I then find the Ins I need to edit in a for loop I edit them, but then I become unstuck because I am not sure how to save the contect of InProject with the *updated Ins
I need to figure out how to save InProject after I have overwritten the Ins attributes I need to update.
This is what my code looks like after battling this problem:
- (void)editSelectedins:(NSString *)projIDString UpdatedNSD:(NSMutableDictionary *)updatedNSD DPC:(int)dpc{
// get context
NSManagedObjectContext *context = [self managedObjectContext];
if (context == nil) {
NSLog(#"Nil");
}
else {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"InsProject" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSMutableArray *InsProjectDictionaryArray = [[NSMutableArray alloc] init];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (InsProject *insProj in fetchedObjects) {
NSMutableDictionary *tempInsProjectDictionaryArray = [[ NSMutableDictionary alloc] init];
[tempInsProjectDictionaryArray setObject:insProj.companyName forKey:#"CompanyName"];
[tempInsProjectDictionaryArray setObject:insProj.projNo forKey:#"ProjNo"];
[tempInsProjectDictionaryArray setObject:insProj.desc forKey:#"Desc"];
[tempInsProjectDictionaryArray setObject:insProj.guid forKey:#"GUID"];
[tempInsProjectDictionaryArray setObject:insProj.projID forKey:#"ProjID"];
[tempInsProjectDictionaryArray setObject:insProj.ins forKey:#"ins"];
[InsProjectDictionaryArray addObject:tempInsProjectDictionaryArray];
}
// now that you have the InsProjects, choose the one you are curently working on in insView using the projectID
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ProjID==%#",projIDString];
[fetchRequest setPredicate:predicate];
// new array with one value that was created using the NSPredicate ProjID
NSArray *tempInsProjectArray = [InsProjectDictionaryArray filteredArrayUsingPredicate:predicate];
// get ins array out of the NSDictionary to edit
NSSet *inssForInsProject = tempInsProjectArray[0][#"ins"];
NSMutableArray *tempAllinss = [[NSMutableArray alloc] init]; // this will contain everything, that means all repeated values are included
for (Items* currItem in [inssForInsProject allObjects]) {
NSArray *keys = [[[currItem entity] attributesByName] allKeys];
NSDictionary *dict = [currItem dictionaryWithValuesForKeys:keys];
[tempAllinss addObject:dict];
}
NSArray *myArray = [tempAllinss copy];
// get the correct items from myArray anything whos dpc matches the dpc parameter of this method
NSMutableArray *editedinsArray = [[NSMutableArray alloc] init];
for (int i = 0; i < [myArray count]; i++) {
NSMutableDictionary *tempinssDictionary = [myArray objectAtIndex:i];
// if you get a match put it into the new editedinsArray to be edited
if ([[tempinssDictionary objectForKey:#"dpc"] integerValue] == dpc) {
[editedinsArray addObject:tempinssDictionary];
}
}
// by now you should have three things
// 1, access to your ins coredata object //this s wrong I actually have access to insProject
// 2, the values you need to be edited saved into a NSArray (editedinsArray, which will be used to check against and keep old values correct)
// 3, UpdatedNSD which will be used to update any values that need to be updated.
// go through your values and update the ins object
int i = 0;
for (ins *temp in editedinsArray) {
NSDictionary *currentEditedins = [editedinsArray objectAtIndex:i];
i++;
// these values should stay the same so use currentEditedins which contains old vals
NSString *stringToNumberDpc = [currentEditedins valueForKey:#"dpc"];
int tempDpcNum = [stringToNumberDpc integerValue];
NSNumber *dpcNumber = [NSNumber numberWithInt:tempDpcNum];
temp.dpc = dpcNumber;
NSString *totDQtyString = [currentEditedins valueForKey:#"totDQty"];
if ((NSNull *)totDQtyString == [NSNull null]) {
temp.totDQty = #"";
} else {
temp.totDQty = totDQtyString;
}
NSString *totShipString = [currentEditedins valueForKey:#"totShip"];
if ((NSNull *)totShipString == [NSNull null]) {
temp.totShip = #"";
} else {
temp.totShip = totShipString;
}
// values to be updated so use updatedNSD wthich was passed in as method param with the new vals
temp.newInsComp = [updatedNSD valueForKey:#"newInsComp"];
temp.newDryComp = [updatedNSD valueForKey:#"newDryComp"];
temp.updatedRow = [updatedNSD valueForKey:#"updatedRow"];
}
#warning --- I have no idea what to do here... i.e. how do I update the tempInsProjectArray.ins values I have just updated in the above for loop then save context which I hope would update insProj and the ins entities involved.
//save
[context save:&error];
}
}
As you can see at the bottom of the code with #warning I explain where I am having the issue. if I log temp inside the for loop I see the updated values perfectly the issue I am having is how do I then update the current tempInsProjectArray.ins values that I have just edited? then save them of course.
Your code is in great need of simplification. Some ground rules:
Use names with smallInitial and camelCase for variables. So not InsProjectDictionaryArray but insProjectDictionaryArray.
The same applies to dictionary keys indicating attribute names of managed objects. So projNo, not ProjNo.
Avoid cryptic abbreviations. Use plain and readable English Not projNo but projectNumber. What is an Ins? What is "dcp"?
Don't use the plural form for entity names. An suitable name for an item is Item, not Items
Don't use the mutable versions of dictionary and array when immutable ones would do.
Avoid duplicating your data, such as in [array copy].
Avoid dictionaries when you have an object graph. The object graph is what core data creates. It renders dictionaries with values and keys unnecessary.
Don't use IDs. The object graph renders those unnecessary as well in most cases. If you use IDs, do not use strings but numbers, such as long ints, or the object version NSNumber.
When fetching data from the Core Data persistent store, don't fetch all the data and the filter the result. Fetch only the data you need.
What you want to accomplish can surely be done in a few lines of code. I will try to summarize what you want to do as far as I understand it.
Your data model looks something like this:
Project <----->> Item
Where the items are in a to-many relationship called ins. I will rename this items. I will also assume that you will refactor your IDs to be of type NSNumber.
All the code up to myArray could be substituted with this:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:"Project"];
request.predicate = [NSPredicate predicateWithFormat:#"projectID = %#", projectID];
request.fetchLimit = 1;
NSArray *fetchedObjects = [self.managedObjectContext
executeFetchRequest:request error:nil];
Project *project = fetchedObjects[0];
You now have all items available simply with project.items. I understand that there could be more than one item with a mysterious attribute dcp of type int (i.e. NSNumber for managed objects), that is equal to the dcp parameter passed.
NSSet *matchingItems = [project.items filteredSetUsingPredicate:
[NSPredicate predicateWithFormat:#"dcp = %#", #(dcp)]];
Now it becomes a bit murky. Why do you have type ins in your for loop if the ins are actually of type Item? You then cast them into a dictionary... This should generate a compiler error. Or you have another class called ins instead of Ins??
Anyway, if you stay with the Items you can just update the values with what you pass in your dictionary:
for (Item *item in matchingItems) {
item.newInsComp = [updatedNSD valueForKey:#"newInsComp"];
item.newDryComp = [updatedNSD valueForKey:#"newDryComp"];
item.updatedRow = [updatedNSD valueForKey:#"updatedRow"];
}
[self.managedObjectContext save:nil];
Done!
BTW you could make it even shorter by setting the entity name of the fetch request to "Item" and setting the following predicate:
[NSPredicate predicateWithFormat:#"project.projectID = %# && dcp = %#",
projectID, #(dcp)];
If you know your InProject, then updating your Ins related to that project is a matter of editing property values on your managed objects.
Why not use the predicate to get an NSManagedObject of the InProject, then pull the relationship off of that and edit the values?
NSManagedObjectContext *context = [self managedObjectContext];
if (!context) {
return;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"InsProject" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Set the predicate on the Core Data fetch request instead
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"ProjID==%#",projIDString];
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
// We now have an array that has objects matching the projectIdString
// Might want to do some additional checks if you're only expecting zero or one objects
InsProject *aProject = [fetchedObjects lastObject];
// If we have no project, no point going any further
if ( !aProject ) return;
// On this NSManagedObject is an NSSet property with all related Ins objects
for ( Ins *anIns in aProject.ins ) {
// If our Ins item matches the passed dpc...
if ( [ins.dpc integerValue] == dpc ) {
// ...we have a match, edit properties
ins.dpc = #(dpc);
ins.newInsComp = [updatedNSD valueForKey:#"newInsComp"];
ins.newDryComp = [updatedNSD valueForKey:#"newDryComp"];
ins.updatedRow = [updatedNSD valueForKey:#"updatedRow"];
}
}
// These are managed objects, so saving the context saves all the changes
NSError *saveError;
[context save:&saveError];
if ( saveError ) {
NSLog(#"Save error: %#", [error localizedDescription]);
}
I am using core data and have been using code like this:
[self.form setValue:self.comments.text forKey:#"comments"];
I want to put code like this into a loop, all my coredata names are the same as the property name. How can I say forKey:self.comments.name and get the same outcome as above or something like that?
EDIT:
If this is not possible, is there another way to set a ton of values into coredata from properties? I have 50+ attributes and properties alike that need to be set and would like to avoid using what im doing now.
If you really want it, you may use these functions from objc/runtime.h:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) // To get properties declared by a class.
const char *property_getName(objc_property_t property) // To get the name of one property
Something like this:
unsigned int propCount = 0;
objc_property_t *properties = class_copyPropertyList([self class], &propCount);
for(int idx = 0; idx < propCount; idx++) {
objc_property_t prop = *(properties + idx);
NSString *key = #(property_getName(prop));
NSLog(#"%#", key);
}
There really is no substitute for reading the docs on CoreData as the patterns for use and syntax will not be obvious at all without a little legwork.
That said, you typically fetch an instance of your NSManagedObject subclass from the data store:
NSManagedObjectContext* moc = [delegate managedObjectContext];
NSEntityDescription* description = [NSEntityDescription entityForName:#"Filter" inManagedObjectContext:moc];
NSSortDescriptor* descriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:description];
[request setSortDescriptors:[NSArray arrayWithObject:descriptor]];
NSError *error;
_enabledFilters = [NSMutableArray arrayWithArray:[moc executeFetchRequest:request error:&error]];
if (error) {
NSLog(#"%#",error.localizedDescription);
}
In this example I now have an array of instances of my NSManagedObject called "Filter"
Then you can select the appropriate instance to reference, and access all of it's attributes with simple dot syntax.
Filter* thisFilter = (Filter*)[_displayFilters objectAtIndex:indexPath.row];
cell.label.text = thisFilter.name;
cell.label.backgroundColor = [UIColor clearColor];
NSString*targetName = thisFilter.imageName;
UIImage *image = [UIImage imageNamed:targetName];
cell.image.image = image;
Now I've taken info from my persistent data store, and used it within my app.
Going the other way and writing to an instance within your data store is only slightly different, in that you directly set the attributes of an instance of your NSManagedObject subclass, and then call save on the context to push any changes down to the store.
TL;DR - you owe it to yourself to spend an hour or two with the CoreData docs...
One way would be to declare an array of the attributes yourself.
NSArray *attributes = [NSArray arrayWithObjects:..., #"comments", .., nil]; // or a NSSet
for(NSString *attribute in attributes){
NSString *text = [[self performSelector:NSSelectorFromString(attribute)] text]; // presuming that it's safe to call 'text' on all your properties
[self.form setValue:text forKey:attribute];
}
Or you can use this if you want all the attributes of your core data model.