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?
Related
New to iOS development. So I passed a method with an object address, thinking that I would be altering the same object by doing so:
_posts = [[NSMutableArray alloc]init];
FetchPosts *dataControl = [[FetchPosts alloc]init];
[dataControl accessPosts: _posts];
And the below code receives the passed in object.
-(void)accessPosts: (NSMutableArray *)transition {
//access the posts here.
_readablePosts = transition;
NSURL *url = [NSURL URLWithString: #"private"];
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
_jsonPosts = [[NSMutableData alloc]init];
[_jsonPosts appendData: data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError *error;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:_jsonPosts options:NSJSONReadingAllowFragments error:&error];
for(int i = 0; i<jsonArray.count; i++) {
NSDictionary *element = jsonArray[i];
Posts *newPost = [[Posts alloc]init];
newPost.title = element[#"Title"];
newPost.discovery = element[#"Discovery"];
newPost.summary = element[#"Description"];
newPost.contact = element[#"Contact"];
[_readablePosts addObject:newPost];
}
}
From my perspective, everything seems to be in place properly. However, when I return to my original method, the _posts does not hold the correct object as it should. Am I doing something wrong with my pointers here?
You call this method -
[dataControl accessPosts: _posts];
Which invokes
[NSURLConnection connectionWithRequest:request delegate:self];
This is a non-blocking method, so it returns immediately and calls the delegate methods once it has retrieved data to be processed.
If you access _posts as soon accessPosts returns you will be doing so before the data has been retrieved from the network.
Only after connectionDidFinishLoading: has been called can you access the data in _posts
One solution is to use a delegation pattern. Your outer code would set itself as a delegate to the FetchPosts object, with the delegate method being called from connectionDidFinishLoading. Not only will this address your asynchronous issue it avoid potentially unsafe updating of the NSMutableArray and avoids the use of side-effects.
You will need to create an appropriate protocol in FetchPosts.h but then you can use something like -
-(void) requestPosts {
FetchPosts *dataControl = [[FetchPosts alloc]init];
dataControl.delegate=self;
[dataControl accessPosts];
}
-(void) didReceiveNewPosts:(NSArray *)posts {
// Do something with posts
}
FetchPosts.m
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError *error;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:_jsonPosts options:NSJSONReadingAllowFragments error:&error];
for(int i = 0; i<jsonArray.count; i++) {
NSDictionary *element = jsonArray[i];
Posts *newPost = [[Posts alloc]init];
newPost.title = element[#"Title"];
newPost.discovery = element[#"Discovery"];
newPost.summary = element[#"Description"];
newPost.contact = element[#"Contact"];
[_readablePosts addObject:newPost];
}
if ([self.delegate respondsToSelector:#selector(didReceiveNewPosts:)]) {
[self.delegate didReceiveNewPosts:_readablePosts];
}
}
I think that the json is empty, the method didReceiveData is called each time that the device obtain a chunk of data, this method can be called more times during a downloading. If you create a new mutable data and append data is like you are resetting the NSDAta object.
You should create the Json post object in didReceiveresponse.
I read a lot of docs about this but I can't really understand how it precisely works.
I would like to save my apps data in JSON format on the disc of the phone.
I have a array of objects of this type:
#interface ObjectA : NSObject
#property (strong, nonatomic) NSMutableArray* names1;
#property (strong, nonatomic) NSMutableArray* names2;
#property (strong, nonatomic) NSMutableArray* names3;
#property (strong, nonatomic) NSMutableArray* names4;
#property (strong, nonatomic) NSString* nameObjectA;
#property (assign) int number;
By using JSONModel, how can I transforme a "NSMutableArray *ObjectA" in a JSON file and after that read this file back in the app.
Thanks.
- (id)initWithJSONDictionary:(NSDictionary *)jsonDictionary {
if(self = [self init]) {
// Assign all properties with keyed values from the dictionary
_nameObjectA = [jsonDictionary objectForKey:#"nameAction"];
_number = [[jsonDictionary objectForKey:#"number"]intValue];
_actions1 = [jsonDictionary objectForKey:#"Action1"];
_actions2 = [jsonDictionary objectForKey:#"Action2"];
_actions3 = [jsonDictionary objectForKey:#"Action3"];
_actions4 = [jsonDictionary objectForKey:#"Action4"];
}
return self;
}
- (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 *actions = [[NSMutableArray alloc] init];
// Get an array of dictionaries with the key "actions"
NSArray *array = [jsonDictionary objectForKey:#"actions"];
// 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
Action *action = [[Action alloc] initWithJSONDictionary:dict];
// Add the Location object to the array
[actions addObject:action];
}
// Return the array of actions objects
return actions;
}
The demo app that comes with JSONModel includes an example how to store your app's data via a JSONMOdel: https://github.com/icanzilb/JSONModel
Check the code in this view controller: https://github.com/icanzilb/JSONModel/blob/master/JSONModelDemo_iOS/StorageViewController.m
The logic is that you can export your model to a json string or json compliant dictionary and then save those to the disc using the standard APIs. Check the code
In ObjectA you define two methods -- toDictionary and initWithDictionary. Roughly:
-(NSDictionary*) toDictionary {
return #{#"names1":names1, #"names2":names2, #"names3":names3, #"names4":names4, #"nameObjectA":nameObjectA, #"number":#(number)};
}
- (id) initWithDictionary:(NSDictionary*) json {
self = [super init];
if (self) {
self.names1 = json[#"names1];
... etc
self.nameObjectA = json[#"nameObjectA"];
self.number = json[#"number"].intValue;
}
return self;
}
Run the dictionary created by toDictionary through NSJSONSerialization to produce an NSData and write that to a file. To read, fetch the NSData from the file, run back through NSJSONSerialization, and use initWithDictionary.
Of course, this assumes that the contents of your dictionaries are "JSON legal" -- strings, numbers, NSArrays, or other NSDictionarys.
And, if the arrays/dictionaries being initialized are mutable, one should specify the "MutableContainers" option on NSJSONSerialization.
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.
In one of my view controllers I am setting a label based on the "GET" data I receive from a separate NSObject class. Obviously it takes much less time to set the label then it does to fetch the data so the label is always set to nil. How can I insure the label isn't set till the data is done fetching.
This is the method preforming the "getting" in the NSObject class myClass
- (void) doGetURL:(NSString *) urlstring
callBackTarget:(id) target
callBackMethod:(NSString *) method
failedMethod:(NSString *) method_failed
{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:urlstring]];
NSLog(#"-- get URL with cookie : [%#] and hash:(%#)", [self cookie], [self modhash]);
if (cookie && [cookie length] > 0)
{
NSDictionary *properties = [NSDictionary dictionaryWithObjectsAndKeys:
cookieDomain, NSHTTPCookieDomain,
#"/", NSHTTPCookiePath,
#"reddit_session", NSHTTPCookieName,
cookie, NSHTTPCookieValue,
// [cookie stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], NSHTTPCookieValue,
nil];
NSHTTPCookie *http_cookie = [NSHTTPCookie cookieWithProperties:properties];
NSArray* cookies = [NSArray arrayWithObjects: http_cookie, nil];
NSDictionary * headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
[request setAllHTTPHeaderFields:headers];
}
NSURLConnection * connection = [NSURLConnection connectionWithRequest:request delegate:self];
NSString *connectionKey = [NSString stringWithFormat: #"%ld", ((intptr_t) connection)];
NSMutableDictionary *dl = [[NSMutableDictionary alloc] init];
[dl setValue:connectionKey forKey:#"connectionKey"];
if (target && method)
{
[dl setValue:target forKey:#"afterCompleteTarget"];
[dl setValue:method forKey:#"afterCompleteAction"];
}
[dl setValue:method_failed forKey:#"failedNotifyAction"];
[connections setValue:dl forKey:connectionKey];
}
That is being called in another method within myClass
- (void)getUserInfo:(NSString*)user
{
NSString *getString = [NSString stringWithFormat:#"%#/user/%#/about.json",server,user];
[self doGetURL:getString callBackTarget:self callBackMethod:#"userInfoResponse:" failedMethod:#"connectionFailedDialog:"];
}
The call back method:
- (void)userInfoResponse:(id)sender
{
NSLog(#"userInfoResponse in()");
NSData * data = (NSData *) sender;
NSError *error;
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&error];
NSDictionary *response = [json objectForKey:#"data"];
//futureLabelStr is a property of myClass
futureLabelStr = [response objectForKey:#"name"];;
}
then the label is set in the View Controller:
- (void)viewDidLoad
{
[myClass getUserInfo:#"some_user"];
myLabel.txt = myClass.futureLabelStr;
}
Please let me know is I need to add more or anything I tried to organize it as best I could but I might have missed something.
You don't want to "halt" your viewController's viewDidLoad, you want to notify it, when
the information changes.
You could do that by either sending a notification when myClass is done and -userInfoResponse: is called (Look at NSNotificationCenter), or implement a delegate pattern in myClass. You could set your viewController as a delegate for myClass and call a delegate method when myClass is finished fetching on viewController that would itself update the label.
Or, looking at your code, you could set your viewController as the receiver of the callback methods with minimal change to your code, even though that is not the best approach because it violates MVC patterns:
[self doGetURL:getString callBackTarget:viewController callBackMethod:#"userInfoResponse:" failedMethod:#"connectionFailedDialog:"];
You would of course need a reference to viewController in myClass and the viewController would need to implement this methods (which is a MVC pattern violation).
Send the data call on a new thread and finish viewDidLoad as normal. Then use NSNotification center from whoever is fetching this (should be a model) to the viewController saying "hey, I got that label for you, come get it and refresh"
Then the VC will just set the label using the data from the model. Check out this link for using NSNotificationCenter stackoverflow.com/questions/2191594/send-and-receive-messages-through-nsnotificationcenter-in-objective-c.
For multithreading read up on grand central dispatch.
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.