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.
Related
Hi I am pretty new to creating Table View Controllers. I'm having difficulty trying to load data retrieved via Json in the ViewDidLoad section, into a table view controller.
The first thing I noticed is that the array where I eventually put my json data (dataArray) is not available in: numberOfRowsInSection, so I declared it outside viewDidLoad but now see that when I debug the array is empty when it gets to numberOfRowsInSection. I did test the retrieval code beforehand so am sure it works.
I have a couple of questions:
1. why is that dataArray is empty in the other methods?
2. is it fine to load data for displaying in tableviews in ViewDidLoad or should I really be loading this data somewhere else where it is more visible and available for use by the tableview methods?
Here is the code:
NSArray *dataArray = nil;
- (void)viewDidLoad {
[super viewDidLoad];
dataArray = [[NSArray alloc] init];
// Set URL variable with Token
NSString *token = #"99fdf505547d607e65b8b8ff16113337";
NSString *teacherUrl = [NSString stringWithFormat: #"https://covle.com/apps/api/nextdoorteacher/teachers?t=%#",token];
// Set up the session configuration
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
[[session dataTaskWithURL:[NSURL URLWithString:teacherUrl]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSLog(#"response ==> %#", response);
if (!error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200){
NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:&error];
NSLog(#" heres the serialised json ===> %#", jsonData);
// move serialised JSON into Array (as the data contained is in array)
dataArray=(NSArray*)[jsonData copy];
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return dataArray.count;
NSLog(#"Array count is set to ===> %#", dataArray.count);
Any help to understand and fix this would be very much appreciated.
Thanks
For this
NSArray *dataArray=(NSArray*)[jsonData copy];
You are creating a new local variable named dataArray which is NOT the same as the global dataArray which u allocated in viewDidLoad, so just write this
dataArray = (NSArray*)[jsonData copy]; and you will get the array count as expected in numberOfRowsInSection
EDIT:
call [self.tableview reloadData] after dataArray = (NSArray*)[jsonData copy] for calling tableview delegate methods to fire once again!
So I'm trying to make a companion app for a Wordpress site for a friend. I have created a PHP file that queries the wp_posts table of the MySQL database
SELECT * FROM wp_posts ORDER BY post_date_gmt DESC
I have run a cURL command on it and the output iis as expected (the table data in JSON format), so that is not the problem.
I think I've narrowed it down to the class I'm using that gets and populates the data. Here's the class:
#import "KYAPIQueryResultsHandler.h"
#import "KYAPIQueryResults.h"
#interface KYAPIQueryResultsProtocol() {
NSMutableData * _downloadedData;
}
#end
#implementation KYAPIQueryResultsProtocol
-(void)downloadItems {
// Download the data via JSON format from API call
NSURL *JSONFileURL = [NSURL
URLWithString:#"http://jeffstockdale.com/API/pntjx_get_posts.php"];
// Create the request to the API
// NSURLRequest *URLRequest = [[NSURLRequest alloc] initWithURL:JSONFileURL];
NSURLRequest *URLRequest = [[NSURLRequest alloc] initWithURL:JSONFileURL];
// Create the NSURL connection
[NSURLConnection connectionWithRequest:URLRequest delegate:self];
}
#pragma mark NSURLConnectionDataProtocol Methods
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// Initialize the data object
_downloadedData = [[NSMutableData alloc] init];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_downloadedData appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
// Create an array to store the locations
NSMutableArray *_Posts = [[NSMutableArray alloc] init];
// Parse JSON data that came in
NSError *error;
NSArray *JSONArray = [NSJSONSerialization JSONObjectWithData:_downloadedData options:NSJSONReadingAllowFragments error:&error];
// Loop through JSON objects, create question objects and add them to the questions array
for (int i = 0; i < [JSONArray count]; i++) {
NSDictionary *JSONElement = JSONArray[i];
// Create a new location object and set its props to the JSONElement properties
KYAPIQueryResults *newResult = [[KYAPIQueryResults alloc] init];
[newResult setID:JSONElement[#"ID"]];
[newResult setPost_author:JSONElement[#"post_author"]];
[newResult setComment_count:JSONElement[#"comment_count"]];
[newResult setComment_status:JSONElement[#"comment_status"]];
[newResult setGuid:JSONElement[#"guid"]];
[newResult setMenu_order:JSONElement[#"menu_order"]];
[newResult setPing_status:JSONElement[#"ping_status"]];
[newResult setPinged:JSONElement[#"pinged"]];
[newResult setPost_author:JSONElement[#"post_author"]];
[newResult setPost_content:JSONElement[#"post_content"]];
[newResult setPost_content_filtered:JSONElement[#"post_content_filtered"]];
[newResult setPost_date:JSONElement[#"post_date"]];
[newResult setPost_date_gmt:JSONElement[#"post_date_gmt"]];
[newResult setPost_excerpt:JSONElement[#"post_excerpt"]];
[newResult setPost_mime_type:JSONElement[#"post_mime_type"]];
[newResult setPost_modified:JSONElement[#"post_modified"]];
[newResult setPost_modified_gmt:JSONElement[#"post_modified_gmt"]];
[newResult setPost_name:JSONElement[#"post_name"]];
[newResult setPost_parent:JSONElement[#"post_parent"]];
[newResult setPost_password:JSONElement[#"post_password"]];
[newResult setPost_status:JSONElement[#"post_status"]];
[newResult setPost_title:JSONElement[#"post_title"]];
[newResult setPost_type:JSONElement[#"post_type"]];
[newResult setTo_ping:JSONElement[#"to_ping"]];
// Add this question to the locations array
[_Posts addObject:newResult];
// Notify delegate that the class is ready to pass back items
if ([self delegate]) {
[[self delegate] itemsDownloaded:_Posts];
}
}
}
#end
I'm really scratching my head on this one. I've tried putting a NSLog expression to make sure the data is downloading, and it is. Thank you in advance for any help you could offer!
Just checking it now, the requested URL does not return valid JSON. Here's the bit at the end that invalidates it:
<br />
<b>Warning</b>: mysqli_close() expects parameter 1 to be mysqli, object given in <b>/home/content/83/6031183/html/API/pntjx_get_posts.php</b> on line <b>49</b><br />
If you log the NSError passed into NSJSONSerialization, it should say much the same.
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?
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.
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];