I'm trying to modify Ray Wenderlich's tutorial in order to donwload a json file from my web, but I'm stuck since is the first time I'm using AFNetworking.
My best attempt is the following:
#interface LOArticulos (){
NSData *jsonResponse;
}
#end
#implementation LOArticulos
+(LOArticulos *)sharedInstance{
static LOArticulos *_sharedArticles;
static dispatch_once_t once;
dispatch_once(&once, ^{
_sharedArticles = [[LOArticulos alloc]init];
});
return _sharedArticles;
}
-(id)init{
if (self = [super init]) {
_todosLosArticulos = [self loadArticlesFromJSON];
}
return self;
}
- (NSArray *)loadArticlesFromJSON{
NSURL *url = [NSURL URLWithString:#"http://www.xente.mundo-r.com/turkish/json/lakari.json"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
jsonResponse = JSON;
}
failure:^(NSURLRequest *request,NSHTTPURLResponse *response,NSError *error, id JSON)
{
NSLog(#"Request failed with error: %#, %#", error,error.userInfo);
}];
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:jsonResponse options:NSJSONReadingMutableContainers error:nil];
[operation start];
NSMutableArray *articlesArray = [[NSMutableArray alloc]initWithCapacity:jsonArray.count];
for (NSDictionary *articleDictionary in jsonArray) {
LOArticulo *articulo = [[LOArticulo alloc]init];
articulo.ID = articleDictionary[#"id"];
articulo.marca = articleDictionary[#"marca"];
articulo.modelo = articleDictionary[#"modelo"];
articulo.price = articleDictionary[#"precio"];
articulo.categoria = articleDictionary[#"categoria"];
articulo.photoURL = articleDictionary[#"photoUrl"];
[articlesArray addObject:articulo];
}
return articlesArray;
}
#end
Unfortunately it does not work, and I got the following error:
"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'data parameter is nil'"
Please, could you help me?
Thanks
The problem is that jsonResponse is already serialized into an NSDictionary (or NSArray) upon entering the completion block, not NSData as you are expecting. To get around this, use a simple AFHTTPRequestOperation, and serialize the response object yourself using whatever options you wish, such as NSJSONReadingMutableContainers.
Related
About every single tutorial and example on the internet I see shows how to fetch JSON from some url and show it in Tableview. This is not my problem I know how to do that with AFNetworking framework or with native APIs.
My problem is that after I have downloaded the JSON, I want to show some of it in my UIView labels. I have actually succeeded doing this when I was trying to find a way around NSURLSession inability to cache in iOS 8. But I didn't realize that it was synchronous.
Factory.m
+ (Factory *)responseJson
{
static Factory *shared = nil;
shared = [[Factory alloc] init];
NSHTTPURLResponse *response = nil;
NSString *jsonUrlString = [NSString stringWithFormat:#"http://urltojson.com/file.json"];
NSURL *url = [NSURL URLWithString:[jsonUrlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSError *error = nil;
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:10.0];
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error) {
NSLog(#"error");
} else {
//-- JSON Parsing
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:nil];
//NSLog(#"Result = %#",result);
shared.responseJson = result;
}
return shared;
}
My question is that is it possible to use for example AFNetwoking to do the same thing? Am I missing some method that I need to call like in case of a TableView
[self.tableView reloadData];
I would like to use that framework because I need to check Reachability and it seems to implement it already.
Edit as asked to show more code
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self factoryLoad];
[self setupView];
}
- (void)factoryLoad
{
Factory *shared = [Factory responseJson];
self.titles = [shared.responseJson valueForKeyPath:#"data.title"];
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
- (void)setupView
{
self.issueTitleLabel.text = [self.titles objectAtIndex:0];
}
There are a couple oddities in the code you posted.
Factory, which appears to be a singleton class, should be instantiated inside a dispatch_once to ensure thread safety.
In ViewController.m, you are calling factoryLoad on the main thread, which is subsequently calling sendSynchronousRequest on the main thread. Apple's NSURLConnection Documentation warns against calling this function on the main thread as it blocks the thread, making your application unresponsive to user input.
You should not be passing in nil as the error parameter in NSJSONSerialization JSONObjectWithData:.
In your case I would recommend separating the fetching of data from the construction of your singleton object.
Factory.m
+(Factory *)sharedFactory {
static Factory *sharedFactory = nil;
dispatch_once_t onceToken;
dispatch_once(&onceToken, {
sharedFactory = [[Factory alloc] init];
});
}
-(void)fetchDataInBackgroundWithCompletionHandler:(void(^)(NSURLResponse *response,
NSData *data,
NSError *error)
completion {
NSHTTPURLResponse *response = nil;
NSString *jsonUrlString = [NSString stringWithFormat:#"http://urltojson.com/file.json"];
NSURL *url = [NSURL URLWithString:[jsonUrlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:10.0];
NSOperationQueue *downloadQueue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request
queue:downloadQueue
completionHandler:completion];
}
Now you should be able to create a reference to the data with a guarantee that the download request has finished and thus the data will exist.
ViewController.m
-(void)factoryLoad {
[[Factory sharedFactory] fetchDataInBackgroundWithCompletionHandler:^(void)(NSURLResponse *response, NSData *data, NSError *error){
if(!error) {
NSError *error2;
NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error2];
if(error2){ /* handle error */ }
self.titles = [serializedData valueForKeyPath:#"data.title"];
[Factory sharedFactory].responseJSON = serializedData;
}
else {
// handle error
}
}];
}
This will guarantee that the download has completed before you try to access any of the downloaded information. However, I've left a few things out here, including any sort of activity indicator displaying to the user that the app is doing something important in the background. The rest is, uh, left as an exercise to the reader.
Ok I took a deeper investigation into Morgan Chen's answer and how to block.
The example code took some modification but I think It works as it should and is better code.
In Factory.m
+ (Factory *) sharedInstance
{
static Factory *_sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
-(void)fetchDataInBackgroundWithCompletionHandler: (void(^)(BOOL success, NSDictionary *data, NSError *error)) block
{
NSString * baseURL = #"http://jsonurl.com/file.json";
AFHTTPRequestOperationManager * manager = [[AFHTTPRequestOperationManager alloc] init];
__weak AFHTTPRequestOperationManager *weakManager = manager;
NSOperationQueue *operationQueue = manager.operationQueue;
[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(#"internet!");
[weakManager.requestSerializer setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[operationQueue setSuspended:NO];
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(#"no internet");
[weakManager.requestSerializer setCachePolicy:NSURLRequestReturnCacheDataElseLoad];
[operationQueue setSuspended:YES];
break;
default:
break;
}
}];
[manager.reachabilityManager startMonitoring];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager GET:baseURL parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (responseObject && [responseObject isKindOfClass:[NSDictionary class]]) {
block(YES, responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { // invalid request.
NSLog(#"%#", error.localizedDescription);
block(NO, nil, error);
}];
}
In ViewController.m I call this method on viewDidLoad
-(void)factoryLoad
{
[[Factory sharedInstance] fetchDataInBackgroundWithCompletionHandler:^(BOOL success, NSDictionary *data, NSError *error) {
if (success) {
NSLog(#"we have stuff");
self.responseData = data;
self.titles = [self.responseData valueForKeyPath:#"data.title"];
[self setupView];
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
}];
}
I have a class for get xml and parse it.
+ (instancetype)sharedRssNewsLoader
{
static BGMRssNewsLoader *sharedRssNewsLoader = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedRssNewsLoader = [[self alloc] initPrivate];
});
return sharedRssNewsLoader;
}
- (instancetype)initPrivate
{
self = [super init];
//did the superclass's designated initializer succeed?
if (self)
{
self.rssNewsItems = [[NSMutableArray alloc] init];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config
delegate:nil
delegateQueue:nil];
//RSS call
[self fetchFeed];
}
return self;
}
- (void)fetchFeed
{
NSString *requestString = #"http://xxx...";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
self.rssXmlString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
DDLogVerbose(#"rssXmlString: %#", self.rssXmlString);
dispatch_sync(dispatch_get_main_queue(), ^{
[self parseXML:self.rssXmlString];
});}
];
[dataTask resume];
}
//parse XML
- (void)parseXML:(NSString *)source
{
NSError *error = nil;
DDXMLDocument *theDocument = [[DDXMLDocument alloc] initWithXMLString:source
options:0
error:&error];
NSArray *xmlItems = [theDocument nodesForXPath:#"//item"
error:&error];
for(DDXMLElement *itemElement in xmlItems)
{
NSString *itemTitleValue = [[[itemElement elementsForName:#"title"] lastObject] stringValue];
NSString *itemDescriptionValue = [[[itemElement elementsForName:#"title"] lastObject] stringValue];
BGMRssNewsEntry *rssEntry = [[BGMRssNewsEntry alloc] initRssNewsEntryWithTitle:itemTitleValue
rssText:itemDescriptionValue
rssUrl:nil
rssDate:nil
isRssRead:NO];
[self.rssNewsItems addObject:rssEntry];
}
}
//if a programmer calls [[BGMItemsStore alloc] init], let him know the error of his ways
- (instancetype) init
{
#throw [NSException exceptionWithName:#"Singleton"
reason:#"You are wrong! Use +[BBGMRssNewsLoader sharedRssNewsLoader]"
userInfo:nil];
return nil;
}
I would like to show result in the table. So in the other class I do:
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self)
{
//custom initialization
//initialization sharedRssNewsLoader with rssItems array and RSS call
[BGMRssNewsLoader sharedRssNewsLoader];
}
return self;
}
But my table is absolutely clear. I know that I need to switch to main_queue when I am working with NSURLSessionDataTask, I did it. I think that I mess with thread :(
That BGMRssNewsLoader is asynchronously loading the data and in parseXML you are adding entries to the self.rssNewsItems object. But I don't see where it tells the table to reload itself when this asynchronous request done. If your table view's data source tries to immediately retrieve the rssNewsItems array, it will likely be empty until the asynchronous request is complete.
Generally, when you're done loading the data, you'd call [self.tableView reloadData] (either by supplying the tableview as a parameter during the instantiation of the BGMRssNewsLoader, or by having it post a notification that the table view controller is observing, or by supplying a completion block parameter in which you'd perform reloadData).
I'd also suggest temporarily adding a breakpoint or log statement where you add objects to the rssNewsItems array, just to make sure that that the XML feed was successfully retrieved and parsed.
For example, I might retire fetch and define a method like so:
- (void)fetchFeedWithCompletionBlock:(void (^)(NSArray *items, NSError *error))completion
{
NSString *requestString = #"http://xxx...";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
self.rssXmlString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
DDLogVerbose(#"rssXmlString: %#", self.rssXmlString);
dispatch_sync(dispatch_get_main_queue(), ^{
[self parseXML:self.rssXmlString];
if (completion) {
completion(self.rssNewsItems, nil);
}
});
}];
[dataTask resume];
}
I would, by the way, not have the initPrivate method initiate the fetch. You want the caller to be able to supply whatever completion block it wants.
I'd then have the tableview's controller do something like:
[[BGMRssNewsLoader sharedRssNewsLoader] fetchFeedWithCompletionBlock:^(NSArray *items, NSError *error) {
// use the items array and/or error
[self.tableView reloadData];
}];
Clearly, the fetchFeedWithCompletionBlock should actually detect errors and call the completion block with the appropriate NSError (so the table view can decide how it wants to present the error ... the singleton shouldn't be doing anything with the UI itself), but hopefully this illustrates the idea of a network object that provides a completion block to allow the caller to specify what it wants to do when the asynchronous request is complete.
I'm starting to learn Objective-C for iOS Development, and a got a issue that is driving me crazy.
All that I want is to do a request, retrieve e JSON and then set this JSON into an instance property.
-(NSArray *) retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Communication Error: %#", error);
}];
[operation start];
return _JSONObjectsCollection;
}
-(void) setJSONOBjectsCollectionAttribute: (NSArray *) arrayWithCollection
{
NSLog(#"Outside Method %#", arrayWithCollection);
self.JSONObjectsCollection = arrayWithCollection;
}
However, my self.JSONObjectsCollection property are ok inside the block, but outside is always null.
Can you help me guys ?
It's because the setting of JSONObjectsCollection happens asynchronously. So your method is returning JSONObjectsCollection before it is set.
Thus, it might look like:
- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];
// do here whatever you want to do now that you have your array, e.g.
//
// [self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Communication Error: %#", error);
}];
[operation start];
}
Note, retrieveAtEndpoint now has a void return type, but in the completion block, I'm invoking whatever code you want to perform once the JSON objects collection has been updated.
If this is a method inside your model object, but you want to provide an interface by which the view controller can supply a block of code that should be executed upon successful retrieval of the JSON, use a completion block:
- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode completion:(void (^)(NSError *error))completion
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];
if (completion)
{
completion(nil);
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
if (completion)
{
completion(error);
}
}];
[operation start];
}
Or, if you want to simplify your use of a block parameter, you can define a type for the completion block at the start of your model object's .h file (before the #interface block):
typedef void (^RetrievalCompleteBlock)(NSError *);
And then the method is simplified a bit:
- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode completion:(RetrievalCompleteBlock)completion
{
// the code here is like it is above
}
Anyway, regardless of whether you use the typedef or not, the view controller could do something like:
ModelObject *object = ...
NSString *rootNode = ...
[object retrieveAtEndpoint:url withRootNode:rootNode completion:^(NSError *error) {
if (error)
{
// handle the error any way you want, such as
NSLog(#"%s: retrieveAtEndPoint error: %#", __FUNCTION__, error);
}
else
{
// do whatever you want upon successful retrieval of the JSON here
}
}];
The details here will vary based upon how your view controller is accessing the model object, knows that the root node should be, etc. I often will include another parameter to my completion block which is the data being retrieved, but given that you updated your model object and can access it that way, perhaps that's not necessary. I simply don't have enough details about your implementation to know what is right here, so I kept my implementation as minimalist as possible.
But hopefully this illustrates the idea. Give your retrieveAtEndpoint method a completion block parameter, which lets the view controller specify what it wants to do upon completion (or failure) of the communication with the server.
I using AFNetworking to populate a UITableView with youtube videos. YouTube API only allows maximum 50 result for each request. So I have to use multiple URLs to get more than 50 results.
I created a Method which will do AFNetworkings AFJSONRequestOperation on the given URLs.
I think the UITable is getting created before i receive the JSON data. Everything was working perfectly before i created the Method.
This the first time i have created a Method. I have been trying to load more than 50 youtube videos on a UITable for the last few days.Please have a look at my code.
Here is my code, you can also download the entire project from
QQViewController.m
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//[super viewDidLoad];
NSString *urlAsString = #"http://gdata.youtube.com/feeds/api/playlists/PL7CF5B0AC3B1EB1D5?v=2&alt=jsonc&max-results=50";
// I am not sure how i am supposed to populate the uitableview with the second link :(
NSString *urlAsString2 = #"http://gdata.youtube.com/feeds/api/playlists/PL7CF5B0AC3B1EB1D5?v=2&alt=jsonc&max-results=50&start-index=51";
self.myurl = [NSURL URLWithString:urlAsString];
[self getJSONfromURL:self.myurl];
[self.tableView reloadData];
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.videoMetaData = [self.myJSON valueForKeyPath:#"items.video"];
NSLog(#" QQVC video Meta Data %#", self.videoMetaData);
self.allThumbnails = [self.myJSON valueForKeyPath:#"data.items.video.thumbnail"];
// The table need to be reloaded or else we will get an empty table.
[self.tableView reloadData]; // Must Reload
// NSLog(#" video Meta Data %#", self.videoMetaData);
}
// Here is the Method
-(void)getJSONfromURL:(NSURL *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// setup AFNetworking stuff
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// call delegate or processing method on success
// [self.myJSON = (NSArray *)JSON];
self.myJSON = [JSON valueForKey:#"data"];
[self.tableView reloadData];
//NSLog(#" in get JSon method %#", self.myJSON);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
There were several problems in the getJSONfromURL: method. The main problem was that you were defining self.myJSON as [JSON valueForKey:#"data"], but then defining self.allThumbnails as [self.myJSON valueForKeyPath:#"data.items.video.thumbnail"] -- you used "data" again, so self.thumbnails was null. This seems to work ok:
-(void)getJSONfromURL:(NSURL *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// setup AFNetworking stuff
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
self.myJSON = [JSON valueForKey:#"data"];
self.allThumbnails = [self.myJSON valueForKeyPath:#"items.video.thumbnail"];
self.videoMetaData = [self.myJSON valueForKeyPath:#"items.video"];
[self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
}
If you want to load date from multiple URLs, you can do it like this. I added a counter to keep track of the progress through the array of URL strings. The table is reloaded after each download, so it doesn't have to wait for the whole thing to complete before some data shows up.
#interface QWViewController ()
#property (strong,nonatomic) NSArray *urlStrings;
#end
#implementation QWViewController {
int counter;
}
-(void)viewDidLoad {
[super viewDidLoad];
counter = 0;
NSString *urlAsString = #"http://gdata.youtube.com/feeds/api/playlists/PL7CF5B0AC3B1EB1D5?v=2&alt=jsonc&max-results=50";
NSString *urlAsString2 = #"http://gdata.youtube.com/feeds/api/playlists/PL7CF5B0AC3B1EB1D5?v=2&alt=jsonc&max-results=50&start-index=51";
self.urlStrings = #[urlAsString,urlAsString2];
self.allThumbnails = [NSMutableArray array];
self.videoMetaData = [NSMutableArray array];
[self getJSONFromURL:self.urlStrings[0]];
}
-(void)getJSONFromURL:(NSString *) urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
self.myJSON = [JSON valueForKey:#"data"];
[self.allThumbnails addObjectsFromArray:[self.myJSON valueForKeyPath:#"items.video.thumbnail"]];
[self.videoMetaData addObjectsFromArray:[self.myJSON valueForKeyPath:#"items.video"]];
[self.tableView reloadData];
counter += 1;
if (counter < self.urlStrings.count) [self getJSONFromURL:self.urlStrings[counter]];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
}
Move your reload tableview call to here.
-(void)getJSONfromURL:(NSURL *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// setup AFNetworking stuff
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// call delegate or processing method on success
// [self.myJSON = (NSArray *)JSON];
self.myJSON = [JSON valueForKey:#"data"];
[self.tableView reloadData];
//NSLog(#" in get JSon method %#", self.myJSON);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
}
I have some code that makes a http call to a json based webservice. That's working fine but I tried to move the code to it's own class and I have a slight hang up. When I call the method, the main thread just continues to the next command without wait for a response from my method.
Here's some code from the main part of the program
[newcall run];
NSLog(#"%#",[newcall status]);
NSArray *resultarray= [newcall returndata];
for (NSString *element in resultarray) {
NSLog(#"%#",element);
}
My Header
#import "AFHTTPClient.h"
#interface jsoncall : AFHTTPClient
{
NSString* Date;
NSString* apps;
NSString* data1;
NSURL* url;
NSString* Path;
NSArray* returndata;
NSString* status;
}
-(void) setApp: (NSString *)input;
-(void) setData: (NSString *)input;
-(void) setURL: (NSString *)input;
-(void) setPath: (NSString *)input;
-(int) run;
-(NSArray *) returndata;
-(NSString *) status;
#end
My run method
-(int) run
{
__block int success;
NSDictionary* jsonDictionary=[NSDictionary dictionaryWithObject: data1 forKey:#"data"];
NSString* jsonString = [jsonDictionary JSONRepresentation];
AFHTTPClient *httpClient=[[AFHTTPClient alloc] initWithBaseURL:url];
NSDictionary *params =[NSDictionary dictionaryWithObjectsAndKeys:
apps,#"app",
jsonString,#"smpdata",nil];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:Path parameters:params];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSArray *dataarray=[JSON valueForKey:#"Data"];
status= [NSString stringWithFormat:#"%#",[JSON valueForKeyPath:#"Status"]];
NSLog(#"%#",status);
returndata= dataarray;
success=1;
NSLog(#"Success: Made it here");
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error,id JSON)
{
success=0;
NSLog(#"Error: Made it here");
}
];
[operation start];
[operation waitUntilFinished];
return success;
}
The method I used was Asychronous and as such I would have to have chained the next set of processing by calling it from the completion block. When the final app is done, we may do that but there's also this solution.
Waiting for completion block to complete in an AFNetworking request
By switching AFJSONRequestOperation to NSURLConnection, I am able to use Sychronous Mode which means the request has to complete before the thread moves on.
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:Path parameters:params];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error) {
NSLog(#"Error: %#",error);
}
else {
id JSON = AFJSONDecode(data, &error);
NSArray *dataarray=[JSON valueForKey:#"Data"];
status= [NSString stringWithFormat:#"%#",[JSON valueForKeyPath:#"Status"]];
NSLog(#"%#",status);
returndata= dataarray;
}
For our needs Synchronous mode should work fine for now.
However
You should not run a Synchronous web call on an UI thread because it will block and make your UI unresponsive. That means you need to put your call to this class in it's own thread with any other code that relies on it. I believe you want to check out use of NSOperation but I'm a Objective-c noob so I won't be adding an example here.