UITableView reloadData crashes with error [__NSCFConstantString objectForKey:] - objective-c

I am parsing data from server and display this data in my app. This data is a JSON data and it looks like this:
{"getMessages":[{"msgid":"1","message":"Hello.","dateposted":"2012-08-28"}]}
That's when a message is available to be sent, however, if no messages were available, JSON will look like this:
{"status":"No messages available"}
In my app, I use NSJSONSerialization to parse the JSON. Here is how I do it:
if ([data length] > 0)
{
NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:data options
NSJSONReadingMutableContainers error:nil];
if (![parsedData objectForKey:#"getMessages"])
{
[self.messageArray addObject:#"No Messages"];
}
else
{
self.messageArray = (NSMutableArray *)[parsedData objectForKey:#"getMessages"];
}
}
As you can see, when the parsedData has no getMessages key, it will add the No Messages in
self.messageArray, but if it has the key, it will add the values related to it.
self.messageArray was the array I used to populate the messageTable. At the end of the download, I put the code [messageTable reloadData].
The problem is this: Reloading the table works if the parsedData contains the key getMessages. However, if the key was not found, reloading the table crashes.
This is my tableView:cellForRowAtIndexPath method:
NSString *tableIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableIdentifier];
if (cell == nil)
cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:tableIdentifier] autorelease];
if (tableView == messageTable) //I do this since there is another table I am using
{
NSString *string = [NSString stringWithFormat:#"%#", [self.messageArray objectAtIndex:0]];
if ([string isEqualToString:#"No Messages"])
{
cell.textLabel.text = string;
}
else
{
NSDictionary *dict = [self.messageArray objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%#", [dict objectForKey:#"message"]];
}
}
In messageTable, I check first if the first index of self.messageArray is equal to the string "No Messages", this is to let the user know that no messages can be retrieved. If the
string is not equal, it will then assume that the data inside the array is a dictionary and therefore, it will be parsed to display the message.
After making use of breakpoints and logs, I realized that the crash happens while reloading the table. I inserted a breakpoint and a log at the start of the method tableView:cellForRowAtIndexPath but it never even got there. I tried checking the content of the self.messageArray and it does contain "No Messages".
The crash tells me this error: [__NSCFConstantString objectForKey:]: unrecognized selector sent to instance 0x10ed84
I know that this error is telling me that I am calling the method objectForKey in a NSString, but I really don't know why. Can anyone help me here?

try this
[self.messageArray removeAllObjects];
[self.messageArray addObject:[parsedData setObject:#"No Messages" forKey:#"getMessages"]];
mostly the dictionary is not setting for the key.. just check..
Also,
NSDictionary *diction = [self.messageArray objectAtIndex:0];
NSString *string = [NSString stringWithFormat:#"%#", [diction objectForKey:#"getMessages"]];
if ([string isEqualToString:#"No Messages"])
{
cell.textLabel.text = string;
}
else
{
NSDictionary *dict = [self.messageArray objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%#", [dict objectForKey:#"message"]];
}

First make sure that messageArray is defined as NSMutableArray... and if you are reloading your parsedData from time to time then try this
if (![parsedData objectForKey:#"getMessages"])
{
[self.messageArray removeAllObjects];
[self.messageArray addObject:#"No Messages"];
}
else
{
[self.messageArray removeAllObjects];
self.messageArray = [[parsedData objectForKey:#"getMessages"] mutableCopy];
}

Related

Issue with sending over a JSON array and getting multiple arrays within a key - Objective C

I have a response code that will retrieve a JSON object to populate a news feed (table) The issue is when retrieving the response, if there is more than one array within a key value it gives me an error:
-[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0x8c9e0f0
2014-03-08 16:46:17.830 Ripple[46433:70b] *** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason:
'-[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0x8c9e0f0'
Here is the response:
responseString: {"status":"ok","code":0,"requested_data":
[{"name":"Admin Team","title":"Ripple","message"
:"Testing this awesome application out!"
,"time_post":"March 8"}
,{"name":"Admin Team"
,"title":"Ripple","message"
:"no","time_post":""}]}
Now within requested_data that is my main concern. If I only have one array being put into the requested_data key, it works fine but it seems that when it contains more than one array it gives me that above error. The arrays in requested_data are separated with a },{.
Again if I have the requested_data key only containing one array, as follow:
responseString: {"status":"ok","code":0,
"requested_data":{"name":"Admin Team","title"
:"Ripple","message":"no","time_post":""}}
It works completely fine.
Here is where it fails:
cell.textLabel.text = [self.googlePlacesArrayFromAFNetworking objectForKey:#"message"];
Here is my .h file:
#property (strong, nonatomic) NSDictionary *googlePlacesArrayFromAFNetworking;
Here is my .m file:
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:returnData //1
options:kNilOptions
error:&error];
self.googlePlacesArrayFromAFNetworking = [json objectForKey:#"requested_data"];
[self.tableView reloadData];
This is how it gets the JSON and how it takes the requested_data object and sets it to google...
And it gets sent here where it crashes as stated above:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.text = [self.googlePlacesArrayFromAFNetworking objectForKey:#"message"];
if([self.googlePlacesArrayFromAFNetworking objectForKey:#"message"] != NULL)
{
cell.detailTextLabel.text = [NSString stringWithFormat:#"Rating: %# of 5",[self.googlePlacesArrayFromAFNetworking objectForKey:#"name"]];
}
else
{
cell.detailTextLabel.text = [NSString stringWithFormat:#"Not Rated"];
}
return cell;
}
Not sure what the issue might be but I know it has to do with how it is retrieving the data from that requested_data NSDictionary and what I want it to do is only take the messagevalue and than go to the next array.
Suggestions, thoughts?
The problem is that sometimes requested_data contains array, sometimes - dictionary. Probably the best way will be changing response to send array always. In case if you have only one item - send array with one item. So instead of
responseString: {"status":"ok","code":0,
"requested_data":
{"name":"Admin Team","title"
:"Ripple","message":"no","time_post":""}}
should be sent
responseString: {"status":"ok","code":0,
"requested_data":
[{"name":"Admin Team","title"
:"Ripple","message":"no","time_post":""}]}
Replace
#property (strong, nonatomic) NSDictionary *googlePlacesArrayFromAFNetworking;
with
#property (strong, nonatomic) NSArray *googlePlacesArrayFromAFNetworking;
and use it as array:
cell.textLabel.text = [self.googlePlacesArrayFromAFNetworking[indexPath.row] objectForKey:#"message"];
So your tableView:cellForRowAtIndexPath: should look like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.text = [self.googlePlacesArrayFromAFNetworking[indexPath.row] objectForKey:#"message"];
if([self.googlePlacesArrayFromAFNetworking[indexPath.row] objectForKey:#"message"] != NULL)
{
cell.detailTextLabel.text = [NSString stringWithFormat:#"Rating: %# of 5",[self.googlePlacesArrayFromAFNetworking[indexPath.row] objectForKey:#"name"]];
}
else
{
cell.detailTextLabel.text = [NSString stringWithFormat:#"Not Rated"];
}
return cell;
}
And do not forget to add numberOfRowsInSection method:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.googlePlacesArrayFromAFNetworking.count;
}
Alright. [] represent Arrays and {} represent dictionaries.
So the reason it works if it only returns one object is because that object get mapped to a dictionary so:
cell.textLabel.text = [self.googlePlacesArrayFromAFNetworking objectForKey:#"message"];
is perfectly acceptable. But if it returns more than one object, the requested data gets mapped to an array and arrays don't respond to objectForKey:
This is as you say, an array, and when you are trying to approach it, you are using a dictionary approach instead of first an array one:
//Dictionary number 1:
{"name":"Admin Team",
"title":"Ripple",
"message":"Testing this awesome application out!"
,"time_post":"March 8"}
//Dictionary number 2"
,{"name":"Admin Team"
,"title":"Ripple",
"message":"no",
"time_post":""
}
]}
Looks like so:
cell.detailTextLabel.text = [NSString stringWithFormat:#"Rating: %# of 5",
[[self.googlePlacesArrayFromAFNetworking objectAtIndex:1]
objectForKey:#"name"]];
This is a behavior that some web sources have -- if there is only one item it's sent as a dictionary, if there are multiple it's sent as an array.
NSArray* responseArray = json[#"requested_data"];
if ([responseArray isKindOfClass:[NSDictionary class]]) {
responseArray = [NSArray arrayWithObject:responseArray];
}
for (NSDictionary* responseString in responseArray) {
... Process responseString
}
To be a little more specific:
Change googlePlacesArrayFromAFNetworking to be declared as an NSArray.
Then:
NSArray* responseArray = json[#"requested_data"];
if ([responseArray isKindOfClass:[NSDictionary class]]) {
responseArray = [NSArray arrayWithObject:responseArray];
}
self.googlePlacesArrayFromAFNetworking = repsonseArray;
And in cellForRowAtIndexPath:
NSDictionary* place = self.googlePlacesArrayFromAFNetworking[indexPath.row];
cell.textLabel.text = place[#"message"];
if ([place:#"message"] != NULL) {
cell.detailTextLabel.text = [NSString stringWithFormat:#"Rating: %# of 5", place[#"name"]];
...
Calling objectForKey on an NSArray causing it to crash.
You can not assume that the response is always a dictionary. It might be an array as well.
So you need to check if it is an NSArray or NSDictionary before retrieving its content.

Error: Mutating method sent to immutable object for NSMutableArray from JSON file

This seems to be a fairly common problem, but the solutions that I have looked at do not solve the error. I am trying to read an NSMutableArray from a JSON file. Many of the suggestions I have seen involve using mutableCopy or [NSMutableArray arrayWithArray:] but both of these solutions do not fix the problem when using the call replaceObjectAtIndex:withObject: seen below. Please let me know if you have any advice on how to solve this problem.
EDIT: I would also like to add that the inventory list is an NSMutableArray of NSMutableArray objects.
The exact error reads:
Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: '-[__NSCFArray replaceObjectAtIndex:withObject:]:
mutating method sent to immutable object'
I have the property defined as follows at the top of my implementation file:
NSMutableArray *inventoryData;
I am trying to read it from a JSON file as follows:
- (void)readJSON
{
//Code to get dictionary full of saves from JSON file (overworld.json) - includes the file path on the ipad as well as
//the dictionary itself
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *localPath = [[NSString alloc] initWithString:[documentsDirectory stringByAppendingPathComponent:#"savedPaintGameData.json"]];
NSString *filePath = [localPath mutableCopy];
NSError *e = nil;
// Read data from file saved previously - read the raw data from the path, then parse it into a dictionary using JSONObjectWithData
NSData *RawJSON = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&e];
if (RawJSON == nil) {
[self saveGameInitialize];
} else {
NSMutableDictionary *localDictionary = [[NSMutableDictionary alloc] initWithDictionary:[NSJSONSerialization JSONObjectWithData:RawJSON options:NSJSONReadingAllowFragments error:&e]];
NSMutableDictionary *savedDataDictionary = [localDictionary mutableCopy];
//inventoryData = [[savedDataDictionary objectForKey:#"inventory"] mutableCopy];
inventoryData = [NSMutableArray arrayWithArray:[savedDataDictionary objectForKey:#"inventory"]];
}
}
I am then trying to replace an object at the given index of the NSMutableArray as seen here:
- (void)setInventoryData: (NSString *) colorKey: (int) change
{
// Check if inventory already contains the paint and change the amount
bool foundPaint = false;
int newAmount = 100; // Magic number prevents crashing # removal check
for (int i = 0; i < [inventoryData count]; i++) {
NSMutableArray *object = [inventoryData objectAtIndex:i];
if ([[object objectAtIndex:0] isEqualToString:colorKey]) {
newAmount = [[object objectAtIndex:1] integerValue] + change;
[[inventoryData objectAtIndex:i] replaceObjectAtIndex:1 withObject:[NSNumber numberWithInt:newAmount]];
foundPaint = true;
break;
}
}
if (newAmount == 0) {
[self removeInventoryColor:colorKey];
}
}
The issue appears to be surround the depth at which you are working... the mutable versions of containers you are creating only apply to that "level". You are later indexing into that level (i.e. accessing a container one level deeper) which is still immutable. Try passing the NSJSONReadingMutableContainers option when you first unserialize the JSON:
NSUInteger jsonReadingOptions = NSJSONReadingAllowFragments | NSJSONReadingMutableContainers;
NSMutableDictionary *localDictionary = [[NSMutableDictionary alloc] initWithDictionary:[NSJSONSerialization JSONObjectWithData:RawJSON options:jsonReadinOptions error:&e]];

how to avoid NULL value in objective c

Please consider this code:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSURL *url = [NSURL URLWithString:#"http://localhost/faq.php?faqType=2"]; // Modify this to
NSString *jsonreturn = [[NSString alloc] initWithContentsOfURL:url]; // Pulls the URL
NSLog(#"jsonreturn=%#",jsonreturn); // Look at the console and you can see what the restults are
NSData *jsonData = [jsonreturn dataUsingEncoding:NSUTF32BigEndianStringEncoding];
NSError *error = nil;
// In "real" code you should surround this with try and catch
NSDictionary *dict = [[CJSONDeserializer deserializer] deserializeAsDictionary:jsonData error:&error];
if (dict)
{
rows = [[dict objectForKey:#"faq"] retain];
}
[jsonreturn release];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Configure the cell.
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
}
NSDictionary *dict1 = [rows objectAtIndex:indexPath.row];
NSLog(#"%#", dict1);
cell.textLabel.text = [dict1 objectForKey:#"faqQues"];
}
//if it is not getting NULL value then UItableView is ok
{"faq":[{"faqQues":"this is mr.mack?"},{"faqQues":"is he good man?"}]}
//but if the data is like NULL
{"faq":[{"faqQues":"this is mr.mack?"},{"faqQues":null}]} // then it is creating EXEC_BAD_ACCESS error,
so how to avoid NULL or check null value, or how can i fix this issue?
You can query the value before using it.
if ([dict objectForKey:#"faqQues"] == [NSNull null]) {
// value is null, use your own value here
} else {
// good value to use
}
You can also do this while enumerating as well.
for (id value in dict) {
if (value == [NSNull null]) {
// null
}
}
The docs of TouchJSON state, that JSON null values are represented using the NSNull singleton (usually used to represent nil in collections - where nil is not allowed). So you have to check against [NSNull null].
But TouchJSON allows to override the default null object:
CJSONDeserializer *theDeserializer = [CJSONDeserializer deserializer];
theDeserializer.nullObject = NULL;
Details https://github.com/TouchCode/TouchJSON (see the "Avoiding NSNull values in output." section)
My fellow programmer Conrad Kramer actually came up with this: https://gist.github.com/3362607
I would change it to a function and replace NULL values with #""

NSDictionary to TableView

because i'm a newby at Stackoverflow i cannot comment someones anwser yet. (my reputation is 16..). I got a question about this anwser: How do I put this JSON data into my table view? Please help me, I'm living in a nightmare :)
Fulvio sais you have to use [eventNameList addObject:event]; and [eventNameList objectAtIndex:indexPath.row]; to store and get the event data but. addObject is an NSMutableSet method and objectAtIndex:indexPath.row is not. So i cannot use this method to get the data from the NSMutableSet.
Besides that, i can use the count methods neither.
Any Idea's ?
Assuming you have an NSDictionary, you could use the [dictionary allKeys] method to retrieve an array with all keys (lets call it keyArray for now). For the rowCount you could return the count of objects in this keyArray. To get the item that needs to be displayed in the cell you could use [dictionary objectForKey:[keyArray objectAtIndex:indexPath.row]]] to get the appropriate dictionary for the displayed cell.
In code:
// use the keyArray as a datasource ...
NSArray *keyArray = [jsonDictionary allKeys];
// ------------------------- //
// somewhere else in your code ...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [keyArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
// set some cell defaults here (mainly design) ...
}
NSString *key = [keyArray objectAtIndex:indexPath.row];
NSDictionary *dictionary = [jsonDictionary objectForKey:key];
// get values from the dictionary and set the values for the displayed cell ...
return cell;
}
#Tieme: apparantly the URL you use already returns an array, you don't really need to process a dictionary (you could just use the array as the dataSource), check out the following:
SBJSON *json = [[[SBJSON alloc] init] autorelease];
NSURL *url = [NSURL URLWithString:#"http://www.my-bjoeks.nl/competitions/fetchRoutes/25.json"];
NSString *string = [[[NSString alloc] initWithContentsOfURL:url] autorelease];
NSError *jsonError = nil;
id object = [json objectWithString:string error:&jsonError];
if (!jsonError) {
NSLog(#"%#", object);
NSLog(#"%#", [object class]); // seems an array is returned, NOT a dictionary ...
}
// if you need a mutableArray for the tableView, you can convert it.
NSMutableArray *dataArray = [NSMutableArray arrayWithArray:object]
eventNameList should be defined as an NSMutableArray, not an NSMutableSet. NSMutableArray responds to both -addObject (it puts the new object at the end of the array) and -objectAtIndex: and when you think about it, a table view is essentially an ordered list and so is an array whereas a set is not.
LUCKY:)
Assuming that you might be having nsmutablearray of nsdictionary.
In such case you can get data using:
[dictionary objectforkey:#"key"] objectAtIndex:indexpath.row]

Memory errors when trying to create and populate a NSMutableDictionary

I am not a Cocoa developer, but I have been dabbling in it to build some plugins for PhoneGap. This particular plugin method is either 1) crashing the app without saying why or 2) complaining about how I release/don't release an object. I have tried a ton of things on my end, including using an Enumerator instead of the for loop. If anyone can point me in the right direction, that would be awesome. I don't mind legwork:
- (void)getPreferences:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {
NSUInteger argc = [arguments count];
NSString* jsCallback = nil;
if (argc > 0) {
jsCallback = [arguments objectAtIndex:0];
} else {
NSLog(#"Preferences.getPreferences: Missing 1st parameter.");
return;
}
NSDictionary *defaults = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
NSMutableArray *keys = (NSMutableArray *) [options objectForKey:#"keys"];
NSMutableDictionary *values = [[NSMutableDictionary alloc] init];
NSUInteger ky = [keys count];
for (int i = 0; i < ky; i ++) {
#try {
[values setObject:[defaults objectForKey:[keys objectAtIndex:i]] forKey:[keys objectAtIndex:i]];
}
#catch (NSException * err) {
NSLog(#"Error %#", err);
}
}
[keys release];
NSString* jsString = [[NSString alloc] initWithFormat:#"%#(%#);", jsCallback, [values JSONRepresentation]];
[defaults release];
[values release];
[webView stringByEvaluatingJavaScriptFromString:jsString];
[jsString release];
}
Human version:
options contains a dictionary with a single key of "keys"
that key contains an array of strings (that are going to be used as keys for lookup)
I want to loop through that array and
For every value that exists in defaults for that key, copy it to values using the same key
Finally, I want to send that values back as JSON (This part was working when I just passed the entire defaults object in, so I think the JSON method is working)
From your code, it follows that you 'own' objects values and jsString (the ones you created with alloc), so you should release them and not any other.
You can read more on memory management here.
Is this the whole code? Also, what exactly error do you get?
Nikita is right, it looks as though you're overreleasing defaults, which would cause a crash later when the autorelease pool gets released. Also, if I understand what you're trying to do correctly, you could create the values dictionary with a single line of code:
NSDictionary *values = [defaultsDict dictionaryWithValuesForKeys:keys];