I'm using this function to send a request to a server and receive JSON response in an NSDictionary.
- (void)performRequest:(NSString *)aRequest
{
NSString *string=[NSString stringWithFormat:#"%#%#",baseURL,aRequest];
NSURL *url = [NSURL URLWithString: string];
NSLog(#"string%#",string);
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
if (data.length > 0 && connectionError == nil)
{
NSDictionary * greeting = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
NSLog(#"%#",greeting2);
}
}];
}
What I want is to modify it so that it remains a function sending service URL and a separate one where the JSON response comes at a NSDictionary so you can reuse them in other classes.
I used the networking introduced by iOS 7: NSURLSession. This is the networking code I use in a project. But I would suggest you to use AFNetworking instead. That will save you more time on it. It depends on what you want in your application.
+ (void)requestOperationWithMethod:(NSString *)method
withUrl:(NSString *)url
header:(NSDictionary *)header
params:(NSDictionary *)params
completion:(FHCompletionResultBlock)completion {
FHNetworking *network = [FHNetworking shareNetworking];
NSData *bodyData;
NSError *jsonError = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:params options:kNilOptions error:&jsonError];
if (!jsonError) {
bodyData = data;
}
// prepare http request header and body data fields
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
for (NSString *key in header.allKeys) {
[request setValue:header[key] forHTTPHeaderField:key];
}
[request setHTTPMethod:method];
[request setHTTPBody:bodyData];
[request setTimeoutInterval:30.0f];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
[[network.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSError *jsonError = nil;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
if (jsonError)
completion(nil, jsonError);
else {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode >= 400) {
if (json[#"code"] && json[#"error"]) {
NSError *error = [NSError errorWithDomain:#"Error" code:[json[#"code"] integerValue] userInfo:#{NSLocalizedDescriptionKey: NSLocalizedString(json[#"error"], nil)}];
completion(nil, error);
}
else {
NSError *error = [NSError errorWithDomain:#"Error" code:-1 userInfo:#{NSLocalizedDescriptionKey: NSLocalizedString(#"Unknown error", nil)}];
completion(nil, error);
}
}
else {
completion(json, nil);
}
}
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}] resume];
}
You can change your method to take a completionHandler parameter, itself:
- (void)performRequest:(NSString *)aRequest completionHandler:(void(^)(NSDictionary *responseObject, NSError *error))completionHandler
{
NSString *string=[NSString stringWithFormat:#"%#%#",baseURL,aRequest];
NSURL *url = [NSURL URLWithString: string];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError) {
if (data.length > 0 && connectionError == nil) {
NSError *parseError;
NSDictionary *responseObject = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&parseError];
completionHandler(responseObject, parseError);
} else {
completionHandler(nil, error);
}
}];
}
Now you can call it and supply whatever you want in the completion handler:
[self performRequest:requestString completionHandler:^(NSDictionary *responseObject, NSError *error) {
if (responseObject) {
// everything is good; use your NSDictionary here
} else {
// handle error here
}
}];
Related
I was trying to load data for the table using values from server.I am using NSURLSession datatask with completion handler. Whenever it reaches the nsurlsession, it shows error.This is the code which i used for getting data.
- (void)geturl:(NSString *)urlvalue datavalues:(NSString *)string fetchGreetingcompletion:(void (^)(NSDictionary *dictionary, NSError *error))completion{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#%#?%#",common.getUrlPort,urlvalue,common.getappversion]];
NSLog(#"url=%#",url);
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest addValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[urlRequest addValue:common.getauthtoken forHTTPHeaderField:#"Authorization"];
//Create POST Params and add it to HTTPBody
[urlRequest setHTTPMethod:#"GET"];
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *connectionError) {
NSLog(#"Response:%# %#\n", response, connectionError);
if (data.length > 0 && connectionError == nil)
{
NSDictionary *greeting = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSString *code = [NSString stringWithFormat:#"%#",[greeting valueForKey:#"code"]];
if([code isEqualToString:#"-1"]){
[self loaderrorview:greeting];
}
else{
if (completion)
completion(greeting, connectionError);
}
}
else if(data == nil){
NSDictionary *errorDict=[[NSDictionary alloc]initWithObjectsAndKeys:#"Server Connection Failed",#"error", nil];
if (completion)
completion(errorDict,connectionError);
}
else
{
NSDictionary *greeting = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
if (completion)
completion(greeting, connectionError);
}
}];
[dataTask resume];
}
The code which i used for getting data from server:
-(void)getdataexplore{
if (!common.checkIfInternetIsAvailable) {
[self.view makeToast:Nointernetconnection];
} else {
NSLog(#"There is internet connection");
[SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeBlack];
[SVProgressHUD showWithStatus:#"Loading..."];
[apiservice geturl:loadexploredata datavalues:nil fetchGreetingcompletion:^(NSDictionary *dictionary, NSError *error) {
//NSLog(#"Test %# Error %#",dictionary,error);
if(error == nil){
authDictionary = dictionary;
[self loaddata];
}
else{
[SVProgressHUD dismiss];
[view_business makeToast:#"Request timed out" duration:2.0 position:CSToastPositionCenter];
}
}];
}
}
The code which i used for storing server data to array:
-(void)loaddata
{
[SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeBlack];
[SVProgressHUD showWithStatus:#"Loading..."];
//[SVProgressHUD dismiss];
NSString *msg = [authDictionary valueForKey:#"msg"];
NSString *code = [NSString stringWithFormat:#"%#",[authDictionary valueForKey:#"code"]];
if([code isEqualToString:#"201"]){
NSDictionary *explore = [authDictionary valueForKey:#"explore_obj"];
arr_CBcategories = [explore valueForKey:#"cb_categories"];
[common setarrCBCaterory:arr_CBcategories];
arr_CBcategoryid = [arr_CBcategories valueForKey:#"id"];
[common setarrCateroryID:arr_CBcategoryid];
arr_CBcategorytitle = [arr_CBcategories valueForKey:#"title"];
[common setarrCaterorytitle:arr_CBcategorytitle];
arr_CBcategoryslug = [arr_CBcategories valueForKey:#"slug"];
[common setarrCateroryslug:arr_CBcategoryslug];
arr_CBcategoryimage = [arr_CBcategories valueForKey:#"image"];
[common setarrCateroryimage:arr_CBcategoryimage];
arr_CBcategorycode = [arr_CBcategories valueForKey:#"code"];
}
I am getting error like "Unable to run main thread". Any solution for this.
Here is my working code with NSURLConnection sendSynchronousRequest :
+ (Inc*)getData:(NSString*)inUUID {
NSString* urlString = [NSString stringWithFormat:#"/inc/%#", incUUID];
NSURLRequest* request = [[HttpRequest requestWithRelativePath:urlString] toNSMutableURLRequest];
NSDictionary* json = [self getJSONForRequest:request];
return [Inc incFromDictionary:json];
}
+ (NSDictionary*)getJSONForRequest:(NSURLRequest*)request {
NSData* responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
return [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];
}
But, sendSynchronousRequest:request is deprecated.
So that, I used NSURLSessionDataTaskinstead of sendSynchronousRequest. Here, is the code which I implemented:
+ (Inc*)getData1:(NSString*)inUUID {
NSString* urlString = [NSString stringWithFormat:#"/in/%#", inUUID];
NSURLRequest* request = [[HttpRequest requestWithRelativePath:urlString] toNSMutableURLRequest];
//NSDictionary* json = [self getJSONForRequest1:request];
__block NSDictionary* json;
dispatch_async(dispatch_get_main_queue(), ^{
[self getJsonResponse1:request success:^(NSDictionary *responseDict) {
json = [responseDict valueForKeyPath:#"detail"];;
//return [Inc incFromDictionary:json];
} failure:^(NSError *error) {
// error handling here ...
}];
});
return [Inc incFromDictionary:json];
}
+ (void)getJsonResponse1:(NSURLRequest *)urlStr success:(void (^)(NSDictionary *responseDict))success failure:(void(^)(NSError* error))failure
{
NSURLSessionDataTask *dataTask1 = [[NSURLSession sharedSession] dataTaskWithRequest:urlStr completionHandler:^(NSData *data, NSURLResponse *response,
NSError *error) {
NSLog(#"%#",data);
if (error)
failure(error);
else {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#",json);
success(json);
}
}];
[dataTask1 resume]; // Executed First
}
The problem is return statement call in getData1 before finish the api call.
Thanks in advance.
As mentioned in the comments you need a completion handler.
Something like this (untested):
+ (void)getData1:(NSString*)inUUID success:(void (^)(NSDictionary *responseDict))success failure:(void(^)(NSError* error))failure {
NSString* urlString = [NSString stringWithFormat:#"/in/%#", inUUID];
NSURLRequest* request = [[HttpRequest requestWithRelativePath:urlString] toNSMutableURLRequest];
[self getJsonResponse1:request success:^(NSDictionary *responseDict) {
NSDictionary* json = [responseDict valueForKeyPath:#"detail"];
success(json);
} failure:^(NSError *error) {
failure(error);
}];
}
+ (void)getJsonResponse1:(NSURLRequest *)urlStr success:(void (^)(NSDictionary *responseDict))success failure:(void(^)(NSError* error))failure
{
NSURLSessionDataTask *dataTask1 = [[NSURLSession sharedSession] dataTaskWithRequest:urlStr completionHandler:^(NSData *data, NSURLResponse *response,
NSError *error) {
NSLog(#"%#",data);
if (error)
failure(error);
else {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#",json);
success(json);
}
}];
[dataTask1 resume]; // Executed First
}
And to call
[MyClass getData1:#"asdf" success:^(NSDictionary *responseDict) {
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *json = [responseDict valueForKeyPath:#"detail"];
Inc *inc = [Inc incFromDictionary:json];
// do something witrh `inc`
});
} failure:^(NSError *error) {
// error handling here ...
}];
Consider to use instances and instance methods of your class(es) rather than only class methods.
#define BaseURL #"http://oaayush-aayush.rhcloud.com/api/"
#define API_KEY #"1fa448b4c2384c5d0a7a058554964e"
-(void)getMedicineDetailsForID:(NSString *)ID{
NSString *pathComponent = [NSString stringWithFormat:#"medicine_details/?key=%#&id=%#",API_KEY,ID];
NSLog(#"%#",ID);
NSString *requestString = [NSString stringWithFormat:#"%#%#",baseURL,pathComponent];
NSURL *requestURL = [NSURL URLWithString:[requestString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:requestURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
[self.delegate updateDataSourceWith:[dict valueForKeyPath:#"response"]];
NSLog(#"%#",[dict valueForKeyPath:#"response"]);
}];
[dataTask resume];
}
-(void)getMedicineSuggestionsForID:(NSString *)ID{
NSString *pathComponent = [NSString stringWithFormat:#"medicine_suggestions/?key=%#&id=%#&limit=200",API_KEY,ID];
NSString *requestString = [NSString stringWithFormat:#"%#%#",baseURL,pathComponent];
NSURL *requestURL = [NSURL URLWithString:[requestString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL];
request.timeoutInterval = 15.0;
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSError *jsonError;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if (error) {
[self.delegate requestFailedWithError:(int)error.code];
}
else if (statusCode != 200) {
[self.delegate requestFailedWithError:NSURLErrorTimedOut];
}
else if (dict){
[self.delegate updateDataSourceWith:[dict valueForKeyPath:#"response.suggestions"]];
}
}];
[dataTask resume];
}
I have two methods and I need to use a variable from first as input parameter in the second. How can I do it? My code is :
First method
-(NSString*)getResponseData :(NSString*) apiHttp {
NSString *code = #"&code=";
NSString *finalLink = [[NSString alloc] initWithFormat:#"%#%#",apiHttp,phoneNumber];
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:finalLink]];
NSLog(#"%#", finalLink);
__block NSDictionary *json;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
NSLog(#"Async JSON: %#", json);
NSError *error;
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
myString = [jsonDict objectForKey:#"result"];
// NSLog(#"%#", myString);
}];
return myString;
}
Second method:
-(void)showCodeView:(NSString*) ifString{
if([ifString isEqualToString:#"200"]){
aPasswordField.hidden = NO;
[aPasswordField setBorderStyle:UITextBorderStyleLine];
aPasswordField.layer.cornerRadius=1.0f;
aPasswordField.layer.masksToBounds=YES;
aPasswordField.layer.borderColor=[[UIColor whiteColor]CGColor];
aPasswordField.layer.borderWidth= 0.8f;
UIColor *color = [UIColor lightTextColor];
aPasswordField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:#"Код" attributes:#{NSForegroundColorAttributeName: color}];
self.aPasswordField.delegate = self;
}
}
And this is how I call them:
[self getResponseData:apiHttp];
[self showCodeView:myString];
So I can't understand why my myString is null after [self getResponseData:apiHttp]; was called even if my method retutns it.
You are calling two methods after another but are missing that the first one is asynchronous.
When you call sendAsynchronousRequest:queue:completionHandler: it will perform the request asynchronously (not waiting) and call the completion block once it has a response. Since the code doesn't wait for this to happen, getResponseData: immediately returns the current value of myString which is nil if it's not set yet.
You can see how this is working by adding a some log statements before and after each method call:
NSLog(#"Before getResponseData:");
[self getResponseData:apiHttp];
NSLog(#"After getResponseData:");
NSLog(#"Before showCodeView:");
[self showCodeView:myString];
NSLog(#"After showCodeView:");
and the same for the asynchronous request
NSLog(#"Before sendAsynchronousRequest:");
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *connectionError) {
NSLog(#"After sendAsynchronousRequest:");
// the rest of the completion block ...
There are many ways to deal with this. One would be to add a block argument for the getResponseData: method that is called from the completion handler of the request.
If you are unused to working with blocks, a simpler but more tightly coupled alternative is to call [self showCodeView:myString]; from inside of the completion handler.
You want to perform showCodeView only when your asynchronous getResponseData finishes, so implement your own rendition of the completion block pattern:
- (void)getResponseData :(NSString*) apiHttp completionHandler:(void (^)(NSDictionary *, NSError *))completion {
NSString *code = #"&code=";
NSString *finalLink = [[NSString alloc] initWithFormat:#"%#%#",apiHttp,phoneNumber];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:finalLink]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (completion) {
if (connectionError) {
completion(nil, connectionError);
} else {
NSError *parseError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
completion(json, parseError);
}
}
}];
}
Note, I've eliminated that __block variable and changed the return type to void (since this doesn't return anything ... the value is passed back via the completion block).
You can then do:
[self getResponseData:apiHttp completionHandler:^(NSDictionary *json, NSError *error) {
if (error) {
// handle this however appropriate for your app
} else {
NSString *myString = json[#"result"];
[self showCodeView:myString];
}
}];
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
returning UIImage from block
Hi I'm trying to return dictionary of json twitter data so i can use it in my application. How ever it is being called from a async block. I can not save it or return it any thoughts?
-(NSDictionary *)TweetFetcher
{
TWRequest *request = [[TWRequest alloc] initWithURL:
[NSURL URLWithString: #"http://search.twitter.com/search.json?
q=iOS%205&rpp=5&with_twitter_user_id=true&result_type=recent"] parameters:nil
requestMethod:TWRequestMethodGET];
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse
*urlResponse,
NSError *error)
{
if ([urlResponse statusCode] == 200)
{
NSError *error;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseData
options:0 error:&error];
//resultsArray return an array [of dicitionaries<tweets>];
NSArray* resultsArray = [dict objectForKey:#"results"];
for (NSDictionary* internalDict in resultsArray)
NSLog([NSString stringWithFormat:#"%#", [internalDict
objectForKey:#"from_user_name"]]);
----> return dict; // i need this dictionary of json twitter data
}
else
NSLog(#"Twitter error, HTTP response: %i", [urlResponse statusCode]);
}];
}
Thnx in advance!
I feel like I've written a ton of this async code lately.
- (void)tweetFetcherWithCompletion:(void(^)(NSDictionary *dict, NSError *error))completion
{
NSURL *URL = [NSURL URLWithString:#"http://search.twitter.com/search.json?q=iOS%205&rpp=5&with_twitter_user_id=true&result_type=recent"];
TWRequest *request = [[TWRequest alloc] initWithURL:URL parameters:nil requestMethod:TWRequestMethodGET];
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if ([urlResponse statusCode] == 200) {
NSError *error;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
if (error) {
completion(nil, error);
return;
}
//resultsArray return an array [of dicitionaries<tweets>];
NSArray* resultsArray = [dict objectForKey:#"results"];
for (NSDictionary* internalDict in resultsArray)
NSLog(#"%#", [internalDict objectForKey:#"from_user_name"]);
completion(dict, nil);
}
else {
NSLog(#"Twitter error, HTTP response: %i", [urlResponse statusCode]);
completion(nil, error);
}
}];
}
So, instead of calling self.tweetDict = [self TweetFetcher];, you would call it this way.
[self tweetFetcherWithCompletion:^(NSDictionary *dict, NSError *error) {
if (error) {
// Handle Error Somehow
}
self.tweetDict = dict;
// Everything else you need to do with the dictionary.
}];