This question already has answers here:
[__NSCFArray objectForKey:]: unrecognized selector sent to instance
(3 answers)
Closed 8 years ago.
I am trying to fill a UITableView with results from JSON via a url. Im getting a cryptic error ( cryptic to me since this is my first iOS app ).
here is my code:
#import "VideoListViewController.h"
#import "Videos.h"
#import "JSONLoader.h"
#implementation VideoListViewController{
NSArray *_videos;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Create a new JSONLoader with a local file URL
JSONLoader *jsonLoader = [[JSONLoader alloc] init];
NSURL *url = [NSURL URLWithString:#"https://api.wistia.com/v1/medias.json?api_password=1b75e458de33a9b3f99d33f6bf409a7e145c570a&project_id=kx3rkgrv2w"];
// Load the data on a background queue...
// As we are using a local file it's not really necessary, but if we were connecting to an online URL then we'd need it
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_videos = [jsonLoader locationsFromJSONFile:url];
// Now that we have the data, reload the table data on the main UI thread
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
});
}
#pragma mark - Table View Controller Methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"LocationCell"];
Videos *videos = [_videos objectAtIndex:indexPath.row];
cell.textLabel.text = videos.name;
cell.detailTextLabel.text = videos.id;
cell.imageView.image = [UIImage imageNamed:#"chat_video.png"];
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_videos count];
}
#end
It never makes it past the dispatch_async call, it just skips over it and then errors out with this error:
'NSInvalidArgumentException', reason: '-[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0x8c9d0d0'.
The format of the json returned I noticed does not have a selector for the info I am pulling maybe that is the problem?
Thanks,
Sam
EDITED
Here is the JSONLoader method:
- (NSArray *)locationsFromJSONFile:(NSURL *)url {
// Create a NSURLRequest with the given URL
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:30.0];
// Get the data
NSURLResponse *response;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
// Now create a NSDictionary from the JSON data
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
// Create a new array to hold the locations
NSMutableArray *videos = [[NSMutableArray alloc] init];
// Get an array of dictionaries with the key "locations"
NSArray *array = [jsonDictionary objectForKey:#""];
// Iterate through the array of dictionaries
for(NSDictionary *dict in array) {
// Create a new Location object for each one and initialise it with information in the dictionary
Videos *video = [[Videos alloc] initWithJSONDictionary:dict];
// Add the Location object to the array
[videos addObject:video];
}
// Return the array of Location objects
return videos;
}
Most (if not all) Objective-c JSON parsers convert objects to instances of NSDictionary class and arrays to instances of NSArray class.
What happens somewhere in your program is that where it assumes it deals with NSDictionary, it actually has an NSArray. I have no idea how your JSON looks like though, so can't say where it happens. Use breakpoints to find out. The problem could be in that JSONLoader class, so if you made it yourself, it might be good idea to check there. If you still have problems, show us code of locationsFromJSONFile method.
Related
In order to test the JSON handling of my app I have created a test.json file that I want to load into an UITableView in my UIViewController class. I have created the JSON file and made a separate json loading class (JSONLoader) that implements the code:
#import <Foundation/Foundation.h>
#interface JSONLoader : NSObject
//return an array of chat objects from the json file given by url
- (NSArray *)chattersFromJSONFile:(NSURL *)url;
#end
in the .h file, in the .m file I have:
#import "JSONLoader.h"
#import "Chatter.h"
#implementation JSONLoader
- (NSArray *)chattersFromJSONFile:(NSURL *)url {
//create a NSURLRequest with the given URL
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:30.0];
//get data
NSURLResponse *response;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
//create NSDictionary from the JSON data
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
//create new array to hold chatter information
NSMutableArray *localChatters = [[NSMutableArray alloc] init];
//get an Array of dictionaries with the key "chatters"
NSArray *chatterArray = [jsonDictionary objectForKey:#"chatters"];
//iterate through array of dictionaries
for(NSDictionary *dict in chatterArray) {
//create new chatter object for each one and initialize it with info from the dictionary
Chatter *chatter = [[Chatter alloc] initWithJSONDictionary:dict];
//add the chatter to an array
[localChatters addObject:chatter];
}
//return array
return localChatters;
}
#end
Which I believe will work for both a JSON file loaded from a URL (the end goal) and also a JSON file I have in my Xcode project as a test. In the -viewDidLoad of my viewController.m file I use:
//create a new JSONLoader with a local file from URL
JSONLoader *jsonLoader = [[JSONLoader alloc] init];
NSURL *url = [[NSBundle mainBundle] URLForResource:#"test" withExtension:#"json"];
NSLog(#"%#",url);
//load the data on a background queue
//use for when connecting a real URL
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_localChatters = [jsonLoader chattersFromJSONFile:url];
NSLog(#"%#",_localChatters);
//push data on main thread (reload table view once JSON has arrived)
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
});
I import the JSONLoader file and also the class the represents a test JSON object (singular chatter) and in my implementation I declare a NSArray *_localChatters.
I'm pretty convinced this should work, however when I NSLog(...) the array it displays empty (), where it should have a list of class objects. This means the JSON is never being parsed by my JSONLoader in the first place.
Any particular reason this could happen?
I have a method in my "MasterView" () class that parses .json data from a URL then populates a table view with the information. In order to be more organized and group the method with other needed methods I attempted to move it into another NSOject class but it didn't work; no errors, no exceptions the table view simply doesn't populate.
Here is the original method in the "Master Class"
- (void) fetchPosts:
{
NSError *error;
NSData *responseData = [NSData dataWithContentsOfURL:myURL];
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray *objects = [[json objectForKey:#"data"] objectForKey:#"children"];
arr = [[NSMutableArray alloc] init];
for (NSDictionary *object in objects) {
NSString *title = [[object objectForKey:#"data"] objectForKey:#"title"];
//Post is just a random NSObject Class
Post *post = [[Post alloc] init];
post.title = title;
[arr addObject:post];
}
NSLog(#"Called");
[self.tableView reloadData];
}
The Edited Method in the other class:
- (void) fetchPosts:(NSURL *)myURL withPostArray:(NSMutableArray*)postArr andTableView: (UITableView*)tableView
{
NSLog(#"CAlled");
NSError *error;
NSData *responseData = [NSData dataWithContentsOfURL:myURL];
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray *objects = [[json objectForKey:#"data"] objectForKey:#"children"];
postArr = [[NSMutableArray alloc] init];
for (NSDictionary *object in objects) {
NSString *title = [[object objectForKey:#"data"] objectForKey:#"title"];
Post *post = [[Post alloc] init];
post.title = title;
[postArr addObject:post];
}
[tableView reloadData];
}
The original Method that works is called: [self fetchPosts:]; the other is: [MyClass fetchPosts:myUrl withPostArray:arr andTableView:self.tableView];
I edited some information out to make it more readable so please let me know if there is any mistakes.
MyClass.h:
#interface MyClass : NSObject <UITableViewDelegate, UITableViewDataSource>
Setting the datasource in MasterView:
//In ViewDidLoad
_delegate = myClass;
self.tableView.dataSource = _delegate;
self.tableView.delegate = _delegate;
//In .h
#property (strong, nonatomic) MyClass *delegate;
Im getting nothing from the compiler when I call [MyClass fetchPosts:myUrl withPostArray:arr andTableView:self.tableView];
If the table view doesn't populate, then the table view is not getting the needed data through the data source.
It's possible that you didn't set the dataSource of your tableView to the new NSObject you created, or that MasterView is still the dataSource of the tableView.
Also, make sure that this method is actually called and the passed tableView is the one presented in the view.
Edit: You have three solutions:
Assign the data source to the new object you created so it handles updating the table view with data, since it now has the actual data.
Adjust that method to return the parsed data to the MasterView and it calls [self.tableView reloadData]. But this is not really good from MVC's point of view.
The third option requires you to create a UIVieController to handle your MasterView and it should be the dataSource for the table view. The view controller should call the said method from the new object, to retrieve the data and update the table view. i.e. like the 2nd solution, but a view controller will call the method and not the MasterView.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSError *error = nil;
NSURL *urlRequest = [NSURL URLWithString:[NSString stringWithFormat:#"...", URL]];
NSString *json = [NSString stringWithContentsOfURL:urlRequest
encoding:NSASCIIStringEncoding
error:&error];
JKParseOptionFlags options = JKParseOptionStrict;
NSDictionary *results = [json objectFromJSONStringWithParseOptions:options];
NSString *body = [[results objectForKey:#"item"] objectForKey:#"description"];
Article *article = [[Article alloc] initWithTitle:title URL:URL body:body];
[self.articles insertObject:article atIndex:0];
});
Right outside of that I have [self.tableView reloadData]; but if I call NSLog(#"%d", self.articles.count); right after that it returns 0. Why is it not adding it? If I call an NSLog inside that block accessing article's body property it will print it, so the object seemingly gets created fine. And yes, the method that this is in does get called (by viewDidLoad).
The body gets executed asynchronously, so it doesn't start running that body until some time after your function is done. So all you do is put some code on a queue (which will not be run until later), and check if the article got added to the list (which it won't, until later).
If you check inside the code, that is actually checking a while later, when the queue is done running the code...
I can't tell you more without seeing the declaration of your articles object but what this usually means is that the NSMutableArray object you're trying to use is nil. At the same time that you're logging the article's body property, try logging the articles object as well. If you declared your array as
NSMutableArray *articles;
Then it won't work - articles is still nil and can't accept objects. Declare/instantiate using one of the following options:
NSMutableArray *articles = [NSMutableArray array];
or
NSMutableArray *articles = [[NSMutableArray alloc] init];
- (void)viewDidLoad
{
binding.logXMLInOut = YES; // to get logging to the console.
StationDetailsJsonSvc_getAvailableStations *request = [[StationDetailsJsonSvc_getAvailableStations new] autorelease];
request.userName=#"twinkle";
request.password=#"twinkle";
StationDetailsJsonSoap11BindingResponse *resp = [binding getAvailableStationsUsingParameters:request];
for (id mine in resp.bodyParts)
{
if ([mine isKindOfClass:[StationDetailsJsonSvc_getAvailableStationsResponse class]])
{
resultsring = [mine return_];
NSLog(#"list string is%#",resultsring);
}
}
#pragma mark parsing
SBJsonParser *parserq = [[SBJsonParser alloc] init];
//if successful, i can have a look inside parsedJSON - its worked as an NSdictionary and NSArray
results= [parserq objectWithString:resultsring error:nil];
NSLog(#"print %#",results);
for (status in results)
{
NSLog(#"%# ",[status objectForKey:#"1" ]);
events=[status objectForKey:#"1"];
NSLog(#"get%#",events);
NSLog(#"events%#",events);
}
events=[status objectForKey:#"1"];
NSLog(#"post%#",events);
self.navigationController.navigationBarHidden=YES;
[whethertableview reloadData];
[super viewDidLoad];
}
this is my code am not getting tableview contents if i run my app crashes getting [NSCFString count]:unrecognized selector sent to instance
You should not get count on NSString but on arrays
you should call [yourString length] to check if the string has something.
You are trying to get the count of a string , which is crashing the App
There are various improvements you could make with this code, but I think I see the problem:
As you are not using ARC, you need to retain what you take out of the parser:
So instead of:
events=[status objectForKey:#"1"]
You need to do:
events= [[status objectForKey:#"1"] retain];
Your crash is caused by accessing a variable that has already been released. More than likely it is the events variable.
...and to add to this. events is probably an NSArray which 'count' is being called on. And [status objectForKey:#"1"] is returning a string... there are many possibilities which i'm speculating about. If events is an NSArray, this isn't the way to add objects to the array.. you repeatedly call events=[status objectForKey:#"1"]; in a loop too.
I have a situation where my table is loading before getting the data from my webservice.
When I debug it, I can see that my NSMutableArray has 123 objects, but it's not being reflected in the table.
This is my connectionDidFinishLoading method:
-(void)connectionDidFinishLoading:(NSURLConnection *)conn{
myData = [[NSString alloc] initWithData:xData encoding:NSUTF8StringEncoding];
SBJsonParser *jParser = [SBJsonParser new];
NSError *error = nil;
NSArray *jsonObjects = [jParser objectWithString:myData error:&error];
NSMutableArray *books = [[NSMutableArray alloc] init];
for(NSDictionary *dict in jsonObjects)
{
Book *b = [Book new];
b.bookName =[dict objectForKey:#"name"];
b.published = [dict objectForKey:#"published"];
[books addObject:b];
}
self.bookArray = books;
[self.tableView reloadData];
NSLog(#"myData = %#",myData);
}
If I debug this I'm getting my jsonObjects and populating my collection. But I notice my table is still empty.
Just to clarify this is my tableView method:
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
...
Book *b = (Book *)[self.bookArray objectAtIndex:[indexPath row]];
cell.textLabel.text = b.bookName;
....
Can anyone help me with what I'm missing?
You need to consider what is returned by numberOfRowsInSection (and numberOfSections) for the case where there is no data to show and for the case when there is data to show. Every time you call reloadData, the tableView will call these two methods to get the latest information about the data source. If your data has not loaded, then these delegate methods should report appropriate values. If the data is not loaded, you can test for a nil value or do some similar test, and then return appropriate values. Once you data is loaded, the delegate methods should return values based on the size of the contents in the data source.
If you show us your code for these methods, someone can comment on whether this is your problem.
It is because the way to do it is, don't load the table till the contents get downloaded via webservices.
For that don't set the delegates via IB. After the completion of this web request set the delegate and datasource via code and then call reloadData method.Hence we can make the table wait to load till the webcall get processed .Provide some neat activity indicator of custom HUD while data being loaded and that will do the job
Check the calling thread. You cannot have UIKit updates on anything other than mainThread. I think your connectionDidFinishLoading: called on a seperate thread and that is the issue here.
Try
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});