Core Data - JSON (TouchJSON) on iPhone - iphone

I have the following code which seems to go on indefinitely until the app crashes. It seems to happen with the recursion in the datastructureFromManagedObject method. I suspect that this method:
1) looks at the first managed object and follows any relationship property recursively.
2) examines the object at the other end of the relationship found at point 1 and repeats the process.
Is it possible that if managed object A has a to-many relationship with object B and that relationship is two-way (i.e an inverse to-one relationship to A from B - e.g. one department has many employees but each employee has only one department) that the following code gets stuck in infinite recursion as it follows the to-one relationship from object B back to object A and so on.
If so, can anyone provide a fix for this so that I can get my whole object graph of managed objects converted to JSON.
#import "JSONUtils.h"
#implementation JSONUtils
- (NSDictionary*)dataStructureFromManagedObject:(NSManagedObject *)managedObject {
NSDictionary *attributesByName = [[managedObject entity] attributesByName];
NSDictionary *relationshipsByName = [[managedObject entity] relationshipsByName];
//getting the values correspoinding to the attributes collected in attributesByName
NSMutableDictionary *valuesDictionary = [[managedObject dictionaryWithValuesForKeys:[attributesByName allKeys]] mutableCopy];
//sets the name for the entity being encoded to JSON
[valuesDictionary setObject:[[managedObject entity] name] forKey:#"ManagedObjectName"];
NSLog(#"+++++++++++++++++> before the for loop");
//looks at each relationship for the given managed object
for (NSString *relationshipName in [relationshipsByName allKeys]) {
NSLog(#"The relationship name = %#",relationshipName);
NSRelationshipDescription *description = [relationshipsByName objectForKey:relationshipName];
if (![description isToMany]) {
NSLog(#"The relationship is NOT TO MANY!");
[valuesDictionary setObject:[self dataStructureFromManagedObject:[managedObject valueForKey:relationshipName]] forKey:relationshipName];
continue;
}
NSSet *relationshipObjects = [managedObject valueForKey:relationshipName];
NSMutableArray *relationshipArray = [[NSMutableArray alloc] init];
for (NSManagedObject *relationshipObject in relationshipObjects) {
[relationshipArray addObject:[self dataStructureFromManagedObject:relationshipObject]];
}
[valuesDictionary setObject:relationshipArray forKey:relationshipName];
}
return [valuesDictionary autorelease];
}
- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects {
NSMutableArray *dataArray = [[NSArray alloc] init];
for (NSManagedObject *managedObject in managedObjects) {
[dataArray addObject:[self dataStructureFromManagedObject:managedObject]];
}
return [dataArray autorelease];
}
//method to call for obtaining JSON structure - i.e. public interface to this class
- (NSString*)jsonStructureFromManagedObjects:(NSArray*)managedObjects {
NSLog(#"-------------> just before running the recursive method");
NSArray *objectsArray = [self dataStructuresFromManagedObjects:managedObjects];
NSLog(#"-------------> just before running the serialiser");
NSString *jsonString = [[CJSONSerializer serializer] serializeArray:objectsArray];
return jsonString;
}
- (NSManagedObject*)managedObjectFromStructure:(NSDictionary*)structureDictionary withManagedObjectContext:(NSManagedObjectContext*)moc {
NSString *objectName = [structureDictionary objectForKey:#"ManagedObjectName"];
NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:objectName inManagedObjectContext:moc];
[managedObject setValuesForKeysWithDictionary:structureDictionary];
for (NSString *relationshipName in [[[managedObject entity] relationshipsByName] allKeys]) {
NSRelationshipDescription *description = [[[managedObject entity]relationshipsByName] objectForKey:relationshipName];
if (![description isToMany]) {
NSDictionary *childStructureDictionary = [structureDictionary objectForKey:relationshipName];
NSManagedObject *childObject = [self managedObjectFromStructure:childStructureDictionary withManagedObjectContext:moc];
[managedObject setValue:childObject forKey:relationshipName];
continue;
}
NSMutableSet *relationshipSet = [managedObject mutableSetValueForKey:relationshipName];
NSArray *relationshipArray = [structureDictionary objectForKey:relationshipName];
for (NSDictionary *childStructureDictionary in relationshipArray) {
NSManagedObject *childObject = [self managedObjectFromStructure:childStructureDictionary withManagedObjectContext:moc];
[relationshipSet addObject:childObject];
}
}
return managedObject;
}
//method to call for obtaining managed objects from JSON structure - i.e. public interface to this class
- (NSArray*)managedObjectsFromJSONStructure:(NSString *)json withManagedObjectContext:(NSManagedObjectContext*)moc {
NSError *error = nil;
NSArray *structureArray = [[CJSONDeserializer deserializer]
deserializeAsArray:[json dataUsingEncoding:NSUTF32BigEndianStringEncoding]
error:&error];
NSAssert2(error == nil, #"Failed to deserialize\n%#\n%#", [error localizedDescription], json);
NSMutableArray *objectArray = [[NSMutableArray alloc] init];
for (NSDictionary *structureDictionary in structureArray) {
[objectArray addObject:[self managedObjectFromStructure:structureDictionary withManagedObjectContext:moc]];
}
return [objectArray autorelease];
}
#end

I answered this question when you posted a comment on the original thread. You need to make some changes to how the recursion works so that it doesn't go into a loop. There are many ways to do this.
For example, you can change the call to get all relationships to instead call a method in your NSManagedObject subclasses that only returns the relationships that are downstream. In that design ObjectA would return the ObjectB relationship but Object B would not return any (or relationships to ObjectC, etc.). This creates a tree like hierarchy for the recursion to work through.
Follow the logic of the code. It process the object or objects you hand to it and then it walks through every object associated with that first set of objects. You already, from your post, showed that you understand it is a loop. Now you need to break that loop in your code with logic to change it from a loop to a tree.
Also, I realize this may sound like I am pimping my book, I explained how to do avoid this loop in my book in the Multi-threading chapter in the section on exporting recipes.
Update NSDate
That sounds like a bug in the JSON parser that you are using as it should be able to handle dates. However your workaround is viable except you need to convert it on both sides which is a PITA. I would look into your parser and see why it is not translating dates correctly as that is a pretty big omission.

I just wanted to point out a small typo, that caused the code to crash, and hopefully this will save you a few min.
- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects {
NSMutableArray *dataArray = [[NSArray alloc] init];
for (NSManagedObject *managedObject in managedObjects) {
[dataArray addObject:[self dataStructureFromManagedObject:managedObject]];
}
return [dataArray autorelease];
}
The NSMutableArray *dataArray = [[NSArray alloc] init]; // This should be NSMutableArray
really should be NSMutableArray *dataArray = [[NSMutableArray alloc] init];
that is all.
thank you

Related

Modifying content from new dictionary also modifies the parent dictionary data

As I have a requirement to add similar objects into the array, I have created new dictionary in such a way.
NSMutableDictionary* existingStepDict = [[[arrayForSteps objectAtIndex:0] mutableCopy] autorelease];
[arrayForSteps addObject:existingStepDict];
[existingStepDict release];
Now, what happens here is that later when I change something in any one of the dictionary, the other one also gets updated. I require both these dictionaries to behave independently.
For that I went through Deep-copy of dictionaries whose code is like this.
NSMutableDictionary* existingStepDict = [[[arrayForSteps objectAtIndex:0] mutableCopy] autorelease];
NSMutableDictionary* destination = [NSMutableDictionary dictionaryWithCapacity:0];
NSDictionary *deepCopy = [[NSDictionary alloc] initWithDictionary:existingStepDict copyItems: YES];
if (deepCopy) {
[destination addEntriesFromDictionary: deepCopy];
[deepCopy release];
}
//add Properties array to Steps Dictionary
[arrayForSteps addObject:destination];
But this too didn't reflect the difference. I know I am making some minor mistake here.
But could some one help me getting my result?
Thanks a lot!
There's an easy way to get a full deepcopy of an NSDictionary o NSArray using the NSCoding (serialization) protocol.
- (id) deepCopy:(id)mutableObject
{
NSData *buffer = [NSKeyedArchiver archivedDataWithRootObject:mutableObject];
return [NSKeyedUnarchiver unarchiveObjectWithData: buffer];
}
In this way you can duplicate any object plus all the obects it contains in a single step.
when I need a mutable deep copy of a NSDictionary I create a Category with this method:
- (NSMutableDictionary *)mutableDeepCopy
{
NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
NSArray *keys = [self allKeys];
for (id key in keys) {
id oneValue = [self valueForKey:key];
id oneCopy = nil;
if ([oneValue respondsToSelector:#selector(mutableDeepCopy)]) {
oneCopy = [oneValue mutableDeepCopy];
} else if ([oneValue respondsToSelector:#selector(mutableCopy)]) {
oneCopy = [oneValue mutableCopy];
}
if (oneCopy == nil) {
oneCopy = [oneValue copy];
}
[returnDict setValue:oneCopy forKey:key];
}
return returnDict;
}
EDIT
and searching the web I found this, I haven't tested
NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDictionary, kCFPropertyListMutableContainers);

NSMutable Array: Getting "out of scope" Status After Mutable Copying

I have a SOAP service and I generated classes and functions on SudzC.com.
So I'm using the soap functions they generated, it returns an NSMutableArray with objects that are inherited by my custom class(which is generated by them, too).
So far everything's good. My values are getting into the array and I could see any property of any object with one condition: Only inside of the function that's handling the service.
Just to make it clear, here is the code:
- (void)viewDidLoad
{
SDZGeneratedWebService* service = [SDZGeneratedWebService service];
service.logging = YES;
[service callMyData:self action:#selector(callMyDataHandler:) dataId: 1];
[super viewDidLoad];
}
- (void) callMyDataHandler: (id) value {
// Handle errors
if([value isKindOfClass:[NSError class]]) {
NSLog(#"%#", value);
return;
}
// Handle faults
if([value isKindOfClass:[SoapFault class]]) {
NSLog(#"%#", value);
return;
}
// Do something with the NSMutableArray* result
NSMutableArray *result = (NSMutableArray *)value;
MyCustomClass *myObject = [result objectAtIndex:0];
NSLog(#"%#", myObject.myProperty); //Works Great
}
Like I said, so far everything's perfect. But I need to use the data outside of this function.
So in my .h file, I created an array like NSMutableArray *myDataArray;
When I intend to copy the result array to myDataArray, it copies the objects(I can see that the myDataArray.count value is equal to result array's) but all the objects are "out of scope". So I cannot use them.
I also tried to copy all objects by indexes in a for loop, nope, the objects are getting their values, but when I "addObject" to myDataArray, same, out of scope.
What is wrong here? Can't I generate an array of a custom class this way?
Edit: The code I'm generating myDataArray:
myDataArray = [[NSMutableArray alloc] init];
[myDataArray removeAllObjects];
for (int i=0; i<((NSMutableArray *)result).count; i++) {
MyCustomClass *myObject = [result objectAtIndex:i];
[myDataArray addObject:myObject];
[myObject release];
}
[self.tableView reloadData];
} //(End of callMyDataHandler function)
I before tried this way, too:
[myDataArray removeAllObjects];
duyurular = [result mutableCopy];
} //(End of callMyDataHandler function)
You can copy objects from one array to another using this method:
NSArray *source;
NSArray *dst = [[NSArray alloc] initWithArray:source];
In your code you should remove line: [myObject release]; and I would better call [((NSMutableArray *)result) count] rather then using dot notation.

Putting single attribute from group of entity objects into an array

If I have an NSArray of custom objects (in this case Core Data objects), how would I put all the items of a particular attribute in another NSArray. Is there a way I can use blocks?
For instance, if the class is People, and one of the attributes is age, and there are five objects in the array of people, then the final NSArray would just show the ages:
{ 12, 45, 23, 43, 32 }
Order is not important.
EDIT I have added a blocks based implementation too alongwith the selector based implementation.
What you are looking for is something equivalent to the "map" function from the functional world (something which, unfortunately, is not supported by Cocoa out of the box):
#interface NSArray (FunctionalUtils)
- (NSArray *)mapWithSelector:(SEL)selector;
- (NSArray *)mapWithBlock:(id (^)(id obj))block;
#end
And the implementation:
#implementation NSArray (FunctionalUtils)
- (NSArray *)mapWithSelector:(SEL)selector {
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:[self count]];
for (id object in self) {
[result addObject:[object performSelector:selector]];
}
return [result autorelease];
}
- (NSArray *)mapWithBlock:(id (^)(id obj))block {
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:[self count]];
for (id object in self) {
[result addObject:block(object)];
}
return [result autorelease];
}
#end
Now, when you need to "map" from people to their ages, you can just do this:
ages = [people mapWithSelector:#selector(age)];
OR
ages = [people mapWithBlock:^(Person *p) { return [p age]; }];
The result, ages, will be a new NSArray containing just the ages of the people. In general, this will work for any sort of simple mapping operations that you might need.
One caveat: Since it returns an NSArray, the elements inside ages should be NSNumber, not a plain old integer. So for this to work, your -age selector should return an NSNumber, not an int or NSInteger.
Assuming that each object has a method called "age" that returns an NSNumber *, you should be able to do something like the following:
-(NSArray *)createAgeArray:(NSArray *)peopleArray {
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:[peopleArray count]];
[peopleArray enumerateObjectsUsingBlock:^(id person, NSUInteger i, BOOL *stop) {
[result addObject:[person age]];
}];
return [result autorelease];
}

Objective-C iPhone - Ordering data within multiple sections of a UITableView dataSource

For the purpose of asking this question about ordering. The following MyObject class returns an instance with random generated category names.
I use the following dataSource methods:
numberOfSections accessed with [dataSource count].
titleForSection accessed with [[dataSource objectAtIndex:indexPath.section] valueForKey:#"categoryName"].
numberOfRowsInSection accessed with [[[dataSource objectAtIndex:indexPath.section] valueForKey:#"myObjects"] count].
And finally, the MyObject for each row is accessed with [[[dataSource objectAtIndex:indexPath.section] valueForKey:#"myObjects"] objectAtIndex:indexPath.row] on the cellForRowAtIndexPath method.
I use the following code to create a dataSource that displays 9 section categories, however I'm a little stuck on the ordering of these categories and the data within. Assume there's an NSDate property as part of the MyObject class.
Question: How would I go about using this to display the records in descending order?
- (void)createDatasource
{
NSInteger numberOfObjects = 10;
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:numberOfObjects];
NSMutableArray *categories = [NSMutableArray arrayWithCapacity:numberOfObjects];
for (int i = 0; i < numberOfObjects; i++)
{
MyObject *obj = [[MyObject alloc] init];
[objects addObject:obj];
[categories addObject:obj.category];
[obj release];
}
NSSet *set = [NSSet setWithArray:categories];
NSMutableArray *dataSource = [[NSMutableArray alloc] initWithCapacity:[set count]];
for (NSString *categoryString in set)
{
NSMutableDictionary *mainItem = [[NSMutableDictionary alloc] initWithObjectsAndKeys:nil, #"categoryName", nil, #"myObjects", nil];
NSMutableArray *mainItemMyObjects = [NSMutableArray array];
[mainItem setValue:categoryString forKey:#"categoryName"];
for (MyObject *obj in objects)
{
if ([obj.category isEqualToString:categoryString])
{
[mainItemMyObjects addObject:obj];
}
}
[mainItem setValue:mainItemMyObjects forKey:#"myObjects"];
[dataSource addObject:mainItem];
[mainItem release];
}
NSLog (#"objects = %#\ncategories = %#\nset = %#\ndatasource = %#", objects, categories, set, dataSource);
}
Easiest would be to sort your arrays, using NSMutableArray's sorting mutators or NSArray's sorting methods. Otherwise you'd have to construct some sort of mapping from input indices to dataSource indices for use by the various data source methods.
Edit Requested sample code for sorting, something like this should work. I assume you are wanting to sort everything by a property named date on the MyObject.
// First, sort the myObject mutable array in each category
for (NSDictionary *d in dataSource) {
[[d valueForKey:#"myObjects"] sortUsingComparator:^NSComparisonResult(id o1, id o2){
// Compare dates. NSDate's 'compare:' would do ascending order, so if we just
// reverse the order of comparison they'll come out descending.
return [[o2 date] compare:[o1 date]];
}];
}
// Second, sort the categories by the earliest dated object they contain
[dataSource sortUsingComparator:^NSComparisonResult(id o1, id o2){
// Extract the first object from each category's array, which must be the
// earliest it contains due to the previous sort.
MyObject *myObject1 = [[o1 valueForKey:#"myObjects"] objectAtIndex:0];
MyObject *myObject2 = [[o2 valueForKey:#"myObjects"] objectAtIndex:0];
// Compare dates, as above.
return [[myObject2 date] compare:[myObject1 date]];
}];

preparing a core data result set for a grouped uitableview

i've got a NSMutableArray created from a data source object
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
[self setAmountArray: mutableFetchResults];
every object in my mutable array has the two instance variables name and timeadded.
now i want to display all names in a uitableview grouped by the day they are added. for that i wrote the following method
-(NSMutableArray*)arrangeTransfersByDate:(NSMutableArray*)transferArray {
// Setting up objects for this method
NSDate *oldDate = [NSDate dateWithTimeIntervalSince1970:0.0f];
NSDateFormatter *dateComparisonFormatter = [[NSDateFormatter alloc] init];
[dateComparisonFormatter setDateFormat:#"yyyy-MM-dd"];
NSMutableArray *returnArray = [[NSMutableArray alloc] init];
for(transfers *transfer in transferArray) {
if( [[dateComparisonFormatter stringFromDate:[transfer timeadded]] isEqualToString:[dateComparisonFormatter stringFromDate:oldDate]] ) {
if([returnArray count] == 0) {
[returnArray addObject:[NSMutableArray arrayWithObject:transfer]];
} else {
[[returnArray objectAtIndex:[returnArray count]-1] addObject:transfer];
}
} else {
[returnArray addObject:[NSMutableArray arrayWithObject:transfer]];
oldDate = [transfer timeadded];
}
}
//[returnArray release];
[dateComparisonFormatter release];
return returnArray;
}
transferArray is my amountArray where my core data objects are stored.
so this works! but
is there a better way to do this? can you give me something like a "best practise" or simply have a look if there are some memory leaks?
thanks!
edit:
the right answer was NSFetchedResultController and its sectionNameKeyPath.
however i don't wanted to store my data twice a time.
so i created the following getter method in my NSManagedObject.
- (NSString *) pubDate {
[self willAccessValueForKey:#"pubDate"];
NSDateFormatter *dateComparisonFormatter = [[NSDateFormatter alloc] init];
[dateComparisonFormatter setDateFormat:#"dd.MM.YYYY"];
NSString *temp = [dateComparisonFormatter stringFromDate:[self pubTime]];
[dateComparisonFormatter release];
[self didAccessValueForKey:#"pubDate"];
return temp;
}
with this i can sort my tableviewcontroller by date using my FetchedResultController and my pubTime which is a timestamp.
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[self managedObjectContext]
sectionNameKeyPath:#"pubDate"
cacheName:#"transfersRoot"];
thanks to all
Generally when you want to display the results of a Core Data fetch in a UITableView, you use an NSFetchedResultsController. You can choose the attribute by which you want to group your results by specifying a sectionNameKeyPath during initialization.
That said, provided your method works then really it's up to you whether you want to change your code around or not.
But please make sure you [returnArray autorelease] before you return it. It's a generally accepted Objective-C practice that any method without "alloc", "new", or "copy" in the name will return an autoreleased object.