Update CoreData entity obj - objective-c

I have a complex CoreData entity: MY_ENTITY
I receive a Object of type MY_ENTITY from my webService.
In some cases, I need to edit my local CoreData obj (MY_ENTITY) with received obj.
So:
I have OBJ_1 in CoreData
I receive OBJ_2 from WebService.
I need to update OBJ_1 from OBJ_2.
Have I to set all field or can I assign OBJ_1 ObjectID to OBJ_2 and save the context (same Context)?

Since they are two separate instances, you need to move what you want from O2 to O1. You can use a routine such as this to do the move attribute by attribute assuming both objects are of the same Entity class:
// use entity description to get entity attributes and use as keys to get value
// scan attributes
NSDictionary *attributes = [[sourceEntity entity] attributesByName];
for (NSString *attribute in attributes) {
id value = [sourceEntity objectForKey:attribute];
if (value == nil) {
continue;
}
NSAttributeType attributeType = [[attributes objectForKey:attribute] attributeType];
switch (attributeType) {
case NSStringAttributeType:
// value = [value stringValue];
break;
case NSInteger16AttributeType:
case NSInteger32AttributeType:
case NSInteger64AttributeType:
case NSBooleanAttributeType:
value = [NSNumber numberWithInteger:[value integerValue]];
break;
case NSFloatAttributeType:
case NSDecimalAttributeType:
value = [NSNumber numberWithDouble:[value doubleValue]];
break;
case NSDateAttributeType:
if (dateFormatter != nil)
value = [dateFormatter stringFromDate:value];
break;
default:
value = #"";
break;
}
[targetEntity setValue:value forKey:attribute];
}
Note, this is just an example and would need to be cleaned up and have error handling added if you intend to use. Also, if you are getting O2 via a webservice as JSON or XML, then you can use this to simply push the JSON payload into the targetEntity. This assumes your payload attrs align with your entity attrs. In this case you would replace sourceEntity with the JSON payload using a block or equivalent for example:
NSArray *seedData = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:dataPath]
options:kNilOptions
error:&err];
[seedData enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
...
id value = [obj objectForKey:attribute];
...
}

In which format are you receiving OBJ_2 from the Web Service?
Either way, assigning OBJ_2 to OBJ_1 would not work since you would only replace the reference your local variable is pointing to.
To synchronize your local CoreData Entity which the data from the server you will need to modify the attributes of your existing entitiy. Depending on your data model and the format you are receiving OBJ_2 in there are different ways to achieve this.

Related

Parsing a JSON of unknown Structure Objective-C

I am trying to parse this json and am not sure of parsing this because the keys like "vis-progress-control" might change and I am trying to build a general code which parses this type of a json. I am sure that "type" key will be present in the json structure.
NSDictionary *dict = [sData JSONValue];
NSArray *items = [dict valueForKeyPath:#"assets"];
NSLog(#"%#", items);
for (NSString *key in [[dict objectForKey:#"assets"]allKeys]) {
for (NSString *subKey in [[[dict objectForKey:#"assets"] objectForKey:key]allKeys]) {
NSLog(#"Value at subkey:%# is %#\n",subKey,[[[dict objectForKey:#"assets"]objectForKey:key]objectForKey:subKey]);
}
}
I am using the SBJson Library on Github. My actual issue is How do I access "direction", "degrees" etc when I do not know the "vjs-progress-holder" key?
I have also a widgets array nested within a widgets array. How do I get these values as well?
Read about tree traversal. Sounds like you want to traverse the "tree" of nodes and when you find a particular leaf value you will then traverse back up one level and know the parent is the container you want.
So the idea is that once it's parsed from the JSON, forget it's JSON because now it's just a tree in arrays and dictionaries. Traverse it by getting all the keys in the dictionary via allKeys (returns an array of keys) and then iterate through them getting the associated values (using something like (psuedo code):
for ( NSString * key in allkeysarray) {
NSString * val = [dict objectForKey: key];
if ( [val isEqualToString: #"gradient"] )
{
// now you know that this dictionary is the one you're looking for so you can maybe break out of this loop and just use the keys you know reference these values.
break;
}
}
hopefully that's enough to get you going.
Assuming I'm understanding your objective here, it seems like you want to do something like this?
NSDictionary *outerDict = [sData JSONValue];
NSDictionary *assets = outerDict[#"assets"];
for (NSDictionary *asset in [assets allValues]) {
NSString *type = asset[#"type"];
if ([type isEqualToString:#"gradient"]) {
float degrees = [asset[#"degrees"] floatValue];
// and read whatever other values you need for gradients
}
if ([type isEqualToString:#"image"]) {
// and read the appropriate values for images here...
}
}
So I'll make a different assumption here, which is that you want an array of gradients. So then that looks basically like this:
NSDictionary *outerDict = [sData JSONValue];
NSDictionary *assets = outerDict[#"assets"];
NSMutableArray *gradients = [NSMutableArray array];
for (NSDictionary *asset in [assets allValues]) {
NSString *type = asset[#"type"];
if ([type isEqualToString:#"gradient"]) {
// Add this asset to the list of gradients:
[gradients addObject:asset];
}
if ([type isEqualToString:#"image"]) {
// do something similar for images
}
}
Then, after having done that, if you would like to read the "degrees" field for the 4th gradient, for example, you will find it at gradients[3][#"degrees"]

Fetching Arrays From Core Data

I've set up a model with a few string fields and a few array fields. The model saves as such:
- (void)saveLevel:(NSString*)level traps:(NSArray*)traps whirls:(NSArray*)whirls accels:(NSArray*)accels walls:(NSArray*)walls dest:(NSString*)dest jupiter:(NSString*)jupiter rating:(NSNumber*)pRating;
{
if (m_pMOC == nil)
// Retrieves the managed object context
NSManagedObject* pNewLevel = [NSEntityDescription insertNewObjectForEntityForName:#"Level" inManagedObjectContext:m_pMOC];
NSDate* pDate = [NSDate date];
// Must have these four attributes
[pNewLevel setValue:level forKey:#"Level_ID"];
[pNewLevel setValue:jupiter forKey:#"Ball"];
[pNewLevel setValue:pDate forKey:#"Creation_Date"];
[pNewLevel setValue:dest forKey:#"Destination"];
[pNewLevel setValue:pRating forKey:#"Rating"];
// Optional attributes
if ([traps count] != 0)
[pNewLevel setValue:traps forKey:#"Traps"];
if ([whirls count] != 0)
[pNewLevel setValue:whirls forKey:#"Whirls"];
if ([accels count] != 0)
[pNewLevel setValue:accels forKey:#"Accelerators"];
if ([walls count] != 0)
[pNewLevel setValue:walls forKey:#"Walls"];
NSError* pError;
if (![m_pMOC save: &pError])
// etc...
}
I have a DataManager class which handles the fetching/saving from/to Core Data, and inside of the Manager I've verified that when I fetch an entity, the arrays are fetched, but when it's returned to the class that's calling it, the arrays are nil (the strings, on the other hand, arrive just fine). Here's the fetch code and the code that parses out the returned value from the fetch:
- (NSArray*) getLevelWithID:(NSString*)level
{
NSEntityDescription *pEntityDescription = [NSEntityDescription entityForName:#"Level" inManagedObjectContext:m_pMOC];
NSFetchRequest *pRequest = [[[NSFetchRequest alloc] init] autorelease];
[pRequest setEntity:pEntityDescription];
NSPredicate* pPredicate = [NSPredicate predicateWithFormat:#"Level_ID == %#", level];
[pRequest setPredicate: pPredicate];
NSError* pError;
NSArray* pLevels = [m_pMOC executeFetchRequest:pRequest error:&pError];
if (pLevels == nil)
{
NSLog(#"Could not find the level with Level_ID = %#",level);
abort();
}
NSArray* pAccels = [pLevels valueForKey:#"Accelerators"];
NSArray* pTraps = [pLevels valueForKey:#"Traps"];
NSArray* pWalls = [pLevels valueForKey:#"Walls"];
NSArray* pWhirls = [pLevels valueForKey:#"Whirls"];
return pLevels;
}
I set a break point on the last four Arrays, and they have objects in them, but in the function that retrieves them (shown below), they are nil.
- (void) initLevel:(NSArray*)pLevelObjects
{
Level* pLevel = [pLevelObjects objectAtIndex:0];
NSString* pJupiter = pLevel.Ball;
NSString* pDest = pLevel.Destination;
NSArray* pAccels = [pLevel valueForKey:#"Accelerators"];
NSArray* pTraps = [pLevel valueForKey:#"Traps"];
NSArray* pWalls = [pLevel valueForKey:#"Walls"];
NSArray* pWhirls = [pLevel valueForKey:#"Whirls"];
... (other initialization of the level) ...
}
I'm perplexed by this. The string values are there, but the arrays are not. I tried using the dot notation originally (NSArray* pAccels = pLevel.Accelerators, etc), but with the same result.
Ideas?
EDIT: initLevel is called from the view controller:
- (void) startGameWithLevelID:(NSString*)pLevelID
{
NSLog(#"MyViewController - beginGameWithLevelID");
NSArray* pLevel = [[DataManager getDataManager] getLevelWithID:pLevelID];
if (pLevel == nil || [pLevel count] == 0)
{
NSLog(#"Didn't retrieve any level with id %#", pLevelID);
abort();
}
else
{
CGRect newFrame = makeScreen([UIScreen mainScreen].applicationFrame);
GameBoard* pGB = [[GameBoard alloc] initWithFrame: newFrame];
pGB.m_pMyVC = self;
[pGB initLevel: pLevel];
[self.view addSubview: (UIView*)pGB];
[pGB release];
}
}
EDIT: I've newly re-factored the arrays into separate entities with a to-one relationship with the Level; the level has a to-many relationship with these entities. I also changed the names to better adhere to conventions: all attributes and relationships now start with a lowercased letter. I've prefixed the attributes and relationships with a_ and r_ respectively. I'm getting an error when saving "-[NSCFString _isKindOfEntity:]: unrecognized selector sent to instance 0x4d83120". It isn't terribly helpful but I'll let you know when I find the issue. For now, the code for saving looks as such:
- (void)saveLevel:(NSString*)level traps:(NSArray*)traps whirls:(NSArray*)whirls accels:(NSArray*)accels walls:(NSArray*)walls dest:(NSString*)dest jupiter:(NSString*)jupiter rating:(NSNumber*)pRating;
{
if (m_pMOC == nil)
{ // Code to get the managed object context from the delegate
}
NSManagedObject* pNewLevel = [NSEntityDescription insertNewObjectForEntityForName:#"Level" inManagedObjectContext:m_pMOC];
NSManagedObject* pNewBall = [NSEntityDescription insertNewObjectForEntityForName:#"Ball" inManagedObjectContext:m_pMOC];
[pNewBall setValue:jupiter forKey:#"a_Bounds"];
NSManagedObject* pNewDest = [NSEntityDescription insertNewObjectForEntityForName:#"Dest" inManagedObjectContext:m_pMOC];
[pNewDest setValue:dest forKey:#"a_Bounds"];
NSDate* pDate = [NSDate date];
// Must have these four attributes
[pNewLevel setValue:level forKey:#"a_Level_ID"];
[pNewLevel setValue:pDate forKey:#"a_Creation_Date"];
[pNewLevel setValue:pRating forKey:#"a_Rating"];
[pNewLevel setValue:#"Bob Dole" forKey:#"a_CreatedBy"];
[pNewLevel setValue:pNewBall forKey:#"r_Ball"];
[pNewLevel setValue:pNewDest forKey:#"r_Dest"];
// Optional attributes
if ([traps count] != 0)
[[pNewLevel mutableSetValueForKey: #"r_Trap"] addObjectsFromArray: traps];
if ([whirls count] != 0)
[[pNewLevel mutableSetValueForKey: #"r_Whirl"] addObjectsFromArray: whirls];
if ([accels count] != 0)
[[pNewLevel mutableSetValueForKey: #"r_Accel"] addObjectsFromArray: accels];
if ([walls count] != 0)
[[pNewLevel mutableSetValueForKey: #"r_Wall"] addObjectsFromArray: walls];
NSError* pError;
if (![m_pMOC save: &pError])
{ // Error saving
}
else
{ // Successfully saved
}
}
Again, thanks for the help, sorry for the lengthy post but I'm hoping this will help other Core Data newbies (like me) learn something.
FINAL EDIT: Ok, so after refactoring, I finally found a solution to save the arrays as objects (though whether it's the most efficient way I'm not sure). Below is just the code for adding a bunch of "Traps". The array passed into the function is the bounds of the Trap, which will be stored. The relationship is then formed with the Level (note it doesn't have to be set by both the Trap and the Level, just one of them, since Core Data takes care of the other way).
if ([traps count] != 0)
{
for (NSString* pBounds in traps)
{
NSManagedObject* pNewTrap = [NSEntityDescription insertNewObjectForEntityForName:#"Trap" inManagedObjectContext:m_pMOC];
[pNewTrap setValue:pBounds forKey:#"a_Bounds"];
[pNewTrap setValue:pNewLevel forKey:#"r_Level"];
}
}
Possible Array Misuse
In your first snippet, you are calling valueForKey: on an NSArray, and the result is an array, because that code iterates over pLevels, passes your valueForKey: argument to each object inside and combines the values.
It is how NSArray implements Key-Value Coding.
In your second piece of code, where you first fetch one pLevel from the array, you perform your valueForKey: selector on an entity, not on an array. So the result is what's in that particular object.
Probably some of your entities have those fields, so a combined result from all of those has some values, but some particular ones do not.
Possible Core Data Misuse
You say that you keep an array in your Core Data entity. How do you do that? There isn't a special type to hold an array, so normally it has to be stored as binary data with performing encoding using [NSKeyedArchiver archivedDataWithRootObject:array]. It might be that your storing and fetching is incorrect because you're trying to store arrays in non-array fields.
Do you also experience any crashes?

How to sort a Array based on it's object's property?

I have a NSArray of 'generic Objects' which contain the following properties
-name
-id
-type (question, topic or user)
How can I sort this array of generic object based on the generic object's type? E.g. I want to display all generic objects of type 'topic' on the top, followed by 'users' than 'questions'
You'll need to define a custom sorting function, then pass it to an NSArray method that allows custom sorting. For example, using sortedArrayUsingFunction:context:, you might write (assuming your types are NSString instances):
NSInteger customSort(id obj1, id obj2, void *context) {
NSString * type1 = [obj1 type];
NSString * type2 = [obj2 type];
NSArray * order = [NSArray arrayWithObjects:#"topic", #"users", #"questions", nil];
if([type1 isEqualToString:type2]) {
return NSOrderedSame; // same type
} else if([order indexOfObject:type1] < [order indexOfObject:type2]) {
return NSOrderedDescending; // the first type is preferred
} else {
return NSOrderedAscending; // the second type is preferred
}
}
// later...
NSArray * sortedArray = [myGenericArray sortedArrayUsingFunction:customSort
context:NULL];
If your types aren't NSStrings, then just adapt the function as needed - you could replace the strings in the order array with your actual objects, or (if your types are part of an enumeration) do direct comparison and eliminate the order array entirely.

Retrieving values from json using objective-c

I am currently trying to work with json and objective-c however having a bit of difficulty. The following is the json that is being returned
{
sethostname = (
{
msgs = "Updating Apache configuration\nUpdating cPanel license...Done. Update succeeded.\nBuilding global cache for cpanel...Done";
status = 1;
statusmsg = "Hostname Changed to: a.host.name.com";
warns = (
);
});
}
I am able to check that the response is coming back and the key is sethostname however no matter what I try I cannot get for example the value of status or statusmsg. Can anyone point me in the right location. The following is basic code I am using to check that sethostname is returned.
NSError *myError = nil;
NSDictionary *res = [NSJSONSerialization JSONObjectWithData:response options:NSJSONReadingMutableLeaves error:&myError];
NSLog([res description]);
NSArray *arr;
arr = [res allKeys];
if ([arr containsObject:#"sethostname"])
{
NSLog(#"worked");
}
When in doubt, write down the structure of your JSON data. For example:
{
sethostname = (
{
msgs = "Updating Apache configuration\nUpdating cPanel license...Done. Update succeeded.\nBuilding global cache for cpanel...Done";
status = 1;
statusmsg = "Hostname Changed to: a.host.name.com";
warns = (
);
});
}
(which is in NeXTSTEP property list format, actually) means that you have a top-level dictionary. This top-level dictionary contains a key called sethostname whose value is an array. This array is comprised of dictionaries, each dictionary having a set of keys: msgs, status, statusmsg, warns. msgs has a string value, status has a number value, statusmsg has a string value,warns` has an array value:
dictionary (top-level)
sethostname (array of dictionaries)
dictionary (array element)
msgs (string)
status (number)
statusmsg (string)
warns (array)
??? (array element)
Having understood this structure, your code should look like:
NSError *myError = nil;
NSDictionary *res = [NSJSONSerialization JSONObjectWithData:response options:NSJSONReadingMutableLeaves error:&myError];
if (!res) { // JSON parser failed }
// dictionary (top-level)
if (![res isKindOfClass:[NSDictionary class]]) {
// JSON parser hasn't returned a dictionary
}
// sethostname (array of dictionaries)
NSArray *setHostNames = [res objectForKey:#"sethostname"];
// dictionary (array element)
for (NSDictionary *setHostName in setHostNames) {
// status (number)
NSNumber *status = [setHostName objectForKey:#"status"];
// statusmsg (string)
NSString *statusmsg = [setHostName objectForKey:#"statusmsg"];
…
}
Why not use the simplest JSON method - [myString jsonValue];
It's part of this JSON framework for objective-c
I don't think if ([arr containsObject:#"sethostname"]) is going to work, because the results array is not going to contain that exact object. It might contain an object with the same content, but it won't be the SAME object.
As jtbandes wrote, you need to log the actually output. NSLog both res and arr and see what you have.

Does core data do its own type casting in the background?

I am working on a simple comparison of two lists to see which items in an "evaluation" list are contained in a larger "target" list. I am getting the data on-the-fly- by parsing two CSV files and storing everything as strings. I successfully import the data into the data store and I can get a list of entities no problem
The problem comes when I actually do a search. Essentially, I am looking for short ISBNs in the form of 1234 from the evaluation list in the target list, which are in the form of 1234-5. The predicate I am using is I am using the CONTAINS comparison in the form of [NSString stringWithFormat:#"%# CONTAINS %#", kOC_Target_PrintBookCode, evalIsbn]
The error I get is the following (grabbed by my NSLog)
NSInvalidArgumentException: Can't look for value (1494) in string (49885); value is not a string
I get the impression that even though the ISBN is being read from a NSString and the Core Data store has the data point spec'd as a String, that Core Data is still doing something in the background with the value for whatever reason it sees fit. Any ideas?
Here is the relevant process logic (though I use that term dubiously) code. Unless otherwise noted in the code, all values being manipulated and/or stored are NSString:
NSArray *evalBooks = [self getEntitiesByName:kOC_EntityName_EvalBook
usingPredicateValue:[NSString stringWithFormat:#"%# > \"\"", kOC_Eval_Bookcode]
withSubstitutionVariables:nil
inModel:[self managedObjectModel]
andContext:[self managedObjectContext]
sortByAttribute:nil];
if ( ( !evalBooks ) || ( [evalBooks count] == 0 ) ) {
// we have problem
NSLog(#"( !evalBooks ) || ( [evalBooks count] == 0 )");
return;
}
[evalBooks retain];
int firstEvalBook = 0;
int thisEvalBook = firstEvalBook;
int lastEvalBook = [evalBooks count]; NSLog(#"lastEvalBook: %i", lastEvalBook);
for (thisEvalBook = firstEvalBook; thisEvalBook < lastEvalBook; thisEvalBook++) {
NSManagedObject *evalBook = [[evalBooks objectAtIndex:thisEvalBook] retain];
NSString *rawIsbn = [[evalBook valueForKey:kOC_Eval_Bookcode] retain];
NSString *isbnRoot = [[self getIsbnRootFromIsbn:rawIsbn] retain];
// this is a custom method I created and use elsewhere without any issues.
NSArray *foundBooks = [self getEntitiesByName:kOC_EntityName_TargetBook
usingPredicateValue:[NSString stringWithFormat:#"%# CONTAINS %#", kOC_Target_PrintBookCode, isbnRoot]
withSubstitutionVariables:nil
inModel:[self managedObjectModel]
andContext:[self managedObjectContext]
sortByAttribute:kOC_Target_PrintBookCode];
if ( foundBooks != nil ) {
[foundBooks retain];
NSLog(#"foundBooks: %lu", [foundBooks count]);
} else {
}
If you're building your predicate as an NSString, I believe
[NSString stringWithFormat:#"%# CONTAINS %#", kOC_Target_PrintBookCode, isbnRoot]
should actually be
[NSString stringWithFormat:#"%# CONTAINS '%#'", kOC_Target_PrintBookCode, isbnRoot]
It seems that you're confusing the way predicateWithFormat: works with the way stringWithFormat: works.
Presumably either kOC_Target_PrintBookCode or isbnRoot is not an object that can be converted to a string. E.g. if either is an integer, the %# operator cannot convert the integer to a string value.