Creating generic Objective C relationship model - objective-c

I'm trying to setup generic model for retrieving data from a backend and processing the data in my iPhone app. I'm trying to accomplish this by creating a base-class for all class in which all logic is defined for retrieving, saving and linking data. This last part is the part I'm struggling on.
In every class I define the object name (in Core Data) and if a certain object has relationships to other objects, also and array of the names of the objects with two arrays (one for hasOne and one for hasMany).
However I can't seem to accomplish cast retrieved objects to a certain class which is stored as a string. This is the code I currently have:
/**
Update the relationsships of an object which has relationsships from the type 'HasOne'.
#param object
Object from which the relations has to be updated.
*/
- (void)updateRelationshipsHasOne:(id)object {
for(NSString *relation in self.hasOne) {
Class class = NSClassFromString(self.table);
id anInstance = [class alloc];
anInstance = object;
NSString *key = [[NSString alloc] initWithFormat:#"%#_id",[relation lowercaseString]];
id delegata = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [delegata managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:relation inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"db_id == %i",[[object valueForKey:key] intValue]];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if(fetchedObjects && [fetchedObjects count] > 0) {
id otherInstance = [fetchedObjects objectAtIndex:0];
SEL s = NSSelectorFromString([[NSString alloc] initWithFormat:#"set%#",relation]);
[anInstance performSelector:s withObject:otherInstance];
}
}
}
If I cast both 'anInstance' and 'otherInstance' to the desired classes, everything goes well. However the whole purpose is to make the method generic, so I need some way to be able to cast both variables to Classes which are stored as strings.
Thanks a lot in advance for any help!
Edit:
The error message I'm receiving is:
2016-03-21 20:34:19.024 DeDamen[12303:834645] -[Heat setEvent]: unrecognized selector sent to instance 0x7fe2829d8cd0
2016-03-21 20:34:19.026 DeDamen[12303:834645] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Heat setEvent]: unrecognized selector sent to instance 0x7fe2829d8cd0'
*** First throw call stack:
(
0 CoreFoundation 0x0000000109e68e65 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x00000001094c0deb objc_exception_throw + 48
2 CoreFoundation 0x0000000109e7148d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x0000000109dbe90a ___forwarding___ + 970
4 CoreFoundation 0x0000000109dbe4b8 _CF_forwarding_prep_0 + 120
5 ******* 0x0000000105d255ab -[CommonBase updateRelationshipsHasOne:] + 1627
6 ******* 0x0000000105d24e60 -[CommonBase updateRelationships:] + 256
7 ******* 0x0000000105d248a9 -[CommonBase save:handler:] + 1065
****
)
libc++abi.dylib: terminating with uncaught exception of type NSException
In here the model 'Heat' refers to the object 'anInstance' and the model 'Event' refers to the object 'otherInstance'. The relations ship is: a Heat has one Event and an Event has many Heats'

Related

JSON request error [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I'm experimenting with JSON for the first time in Objective-C.
Here's the code for my Terminal Application:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
#autoreleasepool
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://itunes.apple.com/search?term=harry&country=us"]];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSError *jsonParsingError = nil;
NSArray *publicTimeline = [NSJSONSerialization JSONObjectWithData:response options:0 error:&jsonParsingError];
NSDictionary *list;
for(int i=0; i<[publicTimeline count];i++)
{
list = [publicTimeline objectAtIndex:i];
NSLog(#"Statuses: %#", [list objectForKey:#"trackName"]);
}
}
return 0;
}
Here's the error I'm getting:
2013-09-11 20:58:55.524 Tweets[7291:303] -[__NSCFDictionary objectAtIndex:]: unrecognized selector sent to instance 0x100391d60
2013-09-11 20:58:55.526 Tweets[7291:303] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary objectAtIndex:]: unrecognized selector sent to instance 0x100391d60'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff91688b06 __exceptionPreprocess + 198
1 libobjc.A.dylib 0x00007fff8bf8a3f0 objc_exception_throw + 43
2 CoreFoundation 0x00007fff9171f40a -[NSObject(NSObject) doesNotRecognizeSelector:] + 186
3 CoreFoundation 0x00007fff9167702e ___forwarding___ + 414
4 CoreFoundation 0x00007fff91676e18 _CF_forwarding_prep_0 + 232
5 Tweets 0x0000000100000d1a main + 378
6 libdyld.dylib 0x00007fff908e67e1 start + 0
)
libc++abi.dylib: terminate called throwing an exception
What's wrong? I have followed a tutorial perfectly but I am the only one getting the error.
I ran your code. My results didn't agree with yours. I didn't crash, but I did receive (null) from the final NSLog call.
Yes, you should be checking the result of +[NSJSONSerialization JSONObjectWithData:options:error]. As #HotLicks has pointed out to you quite rightfully, if the result you get is nil, examining the error object can reveal important clues as to what's gone wrong.
But in this case, it's academic because the result (again, in my case) wasn't nil. The real problem, however, is that you don't appear to have an understanding of your data's schema. jsonObject is a dictionary, but it has only two keys, results and resultCount, and that's the reason you're getting nil from [jsonObject objectForKey:#"trackName"]; trackName isn't one of the keys.
Those two keys that you do get, however, should tell you plenty. It told me enough to try this:
NSArray *results = [jsonObject objectForKey:#"results"];
NSDictionary *dictionary = [results objectAtIndex:0];
id latestLoans = [dictionary objectForKey:#"trackName"];
and got a result:
Harry Potter and the Deathly Hallows - Part 1
In other words, I simply got the first dictionary in the array for results, and asked it for the object it had for trackName.
Since you were originally asking for an array, I'm guessing that you wanted all the values for that particular key, so if you use this:
NSArray *latestLoans = [jsonObject valueForKeyPath:#"results.trackName"];
it provides a list of 50 values corresponding to the trackName key in the dictionaries under the results key.
EDIT:
One other thing you really need to do is to protect yourself against the possibility of nil being returned from your call to -[NSData initWithContentsOfURL:] by checking for it before calling anything else. This alone won't fix your inability to get a response, but it will keep you from crashing. And if you do receive nil, be sure to check the domain and code properties of the error object you get back to get an idea of what's going wrong.

Viewing data retrieved from core data stored in an NSArray

I have an iPad app where data is stored in core data. I have used the following code to retrieve the data from the Observations entity which appears to run without any problems and the count of observationList is correct.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Observations"];
NSError *error = nil;
observationList = [[NSArray alloc]init];
observationList = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
However when I try to access the data with the array - for example:
NSLog(#"%#", [[observationList objectAtIndex:0] objectForKey:#"obsDate"]);
It throws an error
reason: '-[Observations objectForKey:]: unrecognized selector
obsDate exists in core data. Can anyone see what I am doing wrong?
NSManagedObject does not implement objectForKey:, you should be using valueForKey: instead. Your code compiles because the array returns an id so the compiler just has to trust that the method you're calling on it will exist at runtime.
Aside: this line is pointless
observationList = [[NSArray alloc]init];
Because you throw away that array instance on the next line.

What's calling CFStringCreateFromExternalRepresentation?

I'm rather confused by this stack trace (only the confusing part is shown):
-[NSXMLDocument length]: unrecognized selector sent to instance 0x10187e010
An uncaught exception was raised
-[NSXMLDocument length]: unrecognized selector sent to instance 0x10187e010
(
0 CoreFoundation 0x00007fff8f5d6286 __exceptionPreprocess + 198
1 libobjc.A.dylib 0x00007fff9213bd5e objc_exception_throw + 43
2 CoreFoundation 0x00007fff8f6624ce -[NSObject doesNotRecognizeSelector:] + 190
3 CoreFoundation 0x00007fff8f5c3133 ___forwarding___ + 371
4 CoreFoundation 0x00007fff8f5c2f48 _CF_forwarding_prep_0 + 232
5 CoreFoundation 0x00007fff8f548c66 CFDataGetLength + 118
6 CoreFoundation 0x00007fff8f5791df CFStringCreateFromExternalRepresentation + 31
7 asLJ 0x0000000100013828 +[stripHTML stripAllHtmlFromString:] + 212
In particular, I don't understand where the call to CFStringCreateFromExternalRepresentation is happening, so I don't know what part of my code (+[stripHTML stripAllHtmlFromString:]) is causing the exception. What's causing the call to CFStringCreateFromExternalRepresentation? If it's obvious, what is it that I'm doing wrong that's causing the exception? In the future, how can I go about determining what's calling CFStringCreateFromExternalRepresentation?
Here's +[stripHTML stripAllHtmlFromString:]:
+ (NSString *)stripAllHtmlFromString:(NSString *)inputString
{
// based on code from http://sugarmaplesoftware.com/25/strip-html-tags/#comment-71
NSError *theError = NULL;
NSString *modifiedInputString = [NSString stringWithFormat:#"%#\n\n\n\n\n\n\n\n\n\n\n\n",inputString]; // adding some spare newlines at the end to ensure that things will work even with a short non-HTML string
NSXMLDocument *theDocument = [[NSXMLDocument alloc] initWithXMLString:modifiedInputString
options:NSXMLDocumentTidyHTML
error:&theError];
NSString *theXSLTString = #"<?xml version='1.0' encoding='utf-8'?>"
"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:xhtml='http://www.w3.org/1999/xhtml'>"
"<xsl:output method='text'/>"
"<xsl:template match='xhtml:head'></xsl:template>"
"<xsl:template match='xhtml:script'></xsl:template>"
"</xsl:stylesheet>";
NSData *theData = [theDocument objectByApplyingXSLTString:theXSLTString arguments:NULL error:&theError];
[theDocument release];
return [[[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding] autorelease];
}
Oh, actually, probably -objectByApplyingXSLTString:arguments:error: returned an NSXMLDocument and not an NSData. So, the call to -[NSString initWithData:encoding:] is invoking -length on what it thinks is an NSData, but NSXMLDocument doesn't recognize that.
Ken Thomases's answer seems to have it exactly right—for some reason, for very short input (particularly the empty string), -objectByApplyingXSLTString:arguments:error: with the given XSLT returns an NSXMLDocument (even though I don't think it should). To fix it, I first detect whether we got an NSXMLDocument and if so, turn it into a string representation of the XML and feed that back into the method; otherwise assume we got the NSData that we'd originally expected.
Replacing the last 3 lines of the given method (from NSData *theData =... on) with the code below seems to have fixed the issue.
// Had a report of an exception that seemed to indicate objectByApplyingXSLTString:arguments:error: was returning an NSXMLDocument objectinstead of an NSData object, so let's guard against that. (discussed at https://stackoverflow.com/q/10669479/291280 )
NSObject *XSTLresult = [theDocument objectByApplyingXSLTString:theXSLTString arguments:NULL error:&theError];
[theDocument release];
if ([XSTLresult isKindOfClass:[NSXMLDocument class]]) {
// If the result is an NSXMLDocument, call XMLData to get an NSData object, turn it into a string, and feed that back into this method...
return [self stripAllHtmlFromString:[[[NSString alloc]
initWithData:[(NSXMLDocument *)XSTLresult XMLData]
encoding:NSUTF8StringEncoding]
autorelease]
];
} else {
// Otherwise, assume we have an NSData object.
return [[[NSString alloc] initWithData:(NSData *)XSTLresult encoding:NSUTF8StringEncoding] autorelease];
}

Put Core Data values in an array

I need to put the values retrieved using a Fetch Request from my core data graph into an array, but not entirely sure how to go about this.
I'm using the following to perform the fetch:
NSString *entityName = #"Project"; // Put your entity name here
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
// 2 - Request that Entity
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
// 3 - Filter it if you want
request.predicate = [NSPredicate predicateWithFormat:#"belongsToProject = %#", _selectedProject];
// 4 - Sort it if you want
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"dateTaken"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)]];
// 5 - Fetch it
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
[self performFetch];
As you can see, i'm filtering the values that come back using an NSPredicate.
How would I get these values into an array and also be able to select individual attributes for the entity once they are in the array, for example the project.description or project.name?
Thanks to Eimantas I've got the objects in an array, however I still need to do two things:
Loop through the array and output the data into some HTML
Individually select attributes from the array, for example, the project description.
I'm using the following for loop to do the first:
for (int i=0; i < [projectListArray count]; i++)
{
NSString *tmp = (NSString *)[projectListArray objectAtIndex:i];
}
However, this is returning the error:
-[Project length]: unrecognized selector sent to instance 0x1b9f20
2012-03-28 10:48:35.160 Project App[3973:707] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Project length]: unrecognized selector sent to instance 0x1b9f20'
It appears as though i might not be incrementing?
[self.fetchedResultsController fetchedObjects] returns the array of fetched objects.
update
It's better to use fast enumaration, not a for loop:
for (Project *project in projectListArray) {
NSString *projectDescription = [project valueForKey:#"description"];
}
You're getting the exception because you're casting an object to NSString while it's a pointer to (I presume) Project managed object.

Two NSFetchedResultsController, Two Views, One Entity-Type

I'm currently banging my head around this problem:
I have two views on the same entity. The first one lets the user CRUD the Entity (TouchModelVariable), the second one lets the user assign it to a another entity (TouchModelConstraintTerm). The second view is only for selection. But when I delete entities via the first view, upon scrolling in the second view the app crashes with an "index out of bounds" error.
Detailed explanation: First the first controller, the CRUD one.
MSPUIManagedDocument *doc = self.document;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:MODEL_ENTITY_TOUCH_MODEL_VARIABLE inManagedObjectContext:doc.managedObjectContext];
fetchRequest.entity = entityDescription;
fetchRequest.fetchBatchSize = 20;
//[NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:#"touchModel.active"] rightExpression:[NSExpression expressionForConstantValue:YES] modifier:NSDirectPredicateModifier type:nil options:nil];
// touchModel.active == 1
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"touchModel == %#", self.touchModel, nil];
NSSortDescriptor *sortDescriptior = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptior];
fetchRequest.sortDescriptors = sortDescriptors;
// nameSectionIndex
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:doc.managedObjectContext sectionNameKeyPath:#"name" cacheName:#"MSPVariablesManagementTableViewController"];
fetchedResultsController.delegate = self;
NSError *error = nil;
[NSFetchedResultsController deleteCacheWithName:#"MSPVariablesManagementTableViewController"];
if(![fetchedResultsController performFetch:&error]) {
NSLog(#"Error while fetching <%#>, <%#>", error, [error userInfo]);
}
return fetchedResultsController;
The TableView is bound to that controller, as it's described in the book "Pro CoreData for iOS".
The second view uses nearly the same code, but the CacheKeys are different. If I delete entities with the first view, and then navigate through the app to the second view (which is re-instantiated every time) the app crashes upon scrolling, because it assumes that the deleted records are there.
I also tried to save the ManagedObjectContext, before entering the second view.
CRASH: *** -[_PFArray objectAtIndex:]: index (8) beyond bounds (8)
2012-03-17 11:36:48.953 MSPLPSolve[38685:fb03] Stack Trace: (
0 CoreFoundation 0x0192503e __exceptionPreprocess + 206
1 libobjc.A.dylib 0x01dc2cd6 objc_exception_throw + 44
2 CoreFoundation 0x018cda48 +[NSException raise:format:arguments:] + 136
3 CoreFoundation 0x018cd9b9 +[NSException raise:format:] + 57
4 CoreData 0x003ffc23 -[_PFArray objectAtIndex:] + 131
5 CoreData 0x004ec260 -[NSFetchedResultsController objectAtIndexPath:] + 448
6 MSPLPSolve 0x0005d820 -[MSPUtilitiesVariablesSelectionTableViewController configureCell:cellForRowAtIndexPath:] + 128
7 MSPLPSolve 0x0003057b -[MSPFetchedResultsTableViewController tableView:cellForRowAtIndexPath:] + 283
8 UIKit 0x008efc54 -[UITableView(UITableViewInternal) _createPreparedCellForGlobalRow:withIndexPath:] + 494
9 UIKit 0x008f03ce -[UITableView(UITableViewInternal) _createPreparedCellForGlobalRow:] + 69
10 UIKit 0x008dbcbd -[UITableView(_UITableViewPrivate) _updateVisibleCellsNow:] + 1350
11 UIKit 0x008ea6f1 -[UITableView layoutSubviews] + 242
12 UIKit 0x00893d21 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 145
... a load of other stuff not in my code ...
BUT when I save the document completly (closing, reopening) between deleting entities via the first controller and navigating to the second controller - it doesn't crash. Is NSFetchedResultsController caching a loads of stuff in the background? And is there a in depth guide how the caching works and how I have to use it?
Puuuh, thanks for reading this far! Regardless if you have an idea or not!
Mark
Did you do a performFetch on your results controller after you altered the model? Also I'm assuming your results controllers delegate methods are implemented correctly.