I have a method that downloads a binary from a server and returns it.
But before the NSURLSession completes, my function is returning the value, so it's coming to be nil each time.
How can I wait till the download is complete and then return the binary?
Try this -
NSURLSession *delegateFreeSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
[[delegateFreeSession dataTaskWithURL: [NSURL URLWithString: #"http://www.example.com/"]
completionHandler:^(NSData *data, NSURLResponse *response,
NSError *error) {
NSLog(#"Got response %# with error %#.\n", response, error);
NSLog(#"DATA:\n%#\nEND DATA\n",
[[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding]);
[self loadDataToView:data]; // << your custom method inside the MyViewControllerClass
}] resume];
Your method should take a callback as an argument. Once the NSURLSession completion handler gets the object you need, you call that callback with the data (or an NSError object if you got an error back from the server)
You can't 'wait' for execution to continue after you get the data. By definition, such network operations are handled asynchronously, hence the need for a callback.
Update: sample code below
- (void)getFeed:(NSDictionary*)parameters fromURL:(NSString*)url withCallback:(void (^)(NSData *data, NSURLResponse *response, NSError *error))callback {
NSURL*newsfeedUrl = [NSURL URLWithString:url];
NSMutableURLRequest *newsfeedRequest = [NSMutableURLRequest requestWithURL:newsfeedUrl];
[newsfeedRequest addValue:#"XMLHTTPRequest" forHTTPHeaderField:#"X-Requested-With"];
newsfeedRequest.HTTPMethod = #"GET";
NSURLSessionDataTask *downloadNeewsfeedDataTask = [instance.session dataTaskWithRequest:newsfeedRequest completionHandler:^(NSData*data, NSURLResponse*response, NSError*error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) response;
NSLog(#"statusCode %ld", (long)httpResponse.statusCode);
//Do something here in your completion handler and call your callback function when ready (either with the data you expected, or with an error object. Alternatively, you can outright refrain from adding a completion handler block and simply put your callback argument as the completion handler (they must have the same signature).
callback(data, response, error);
}];
[downloadNeewsfeedDataTask resume];
}
Here is the example of completion handler for NSURLSession.
-(void) httpPostWithCustomDelegate{
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSURL * url = [NSURL URLWithString:#"http://hayageek.com/examples/jquery/ajax-post/ajax-post.php"];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
NSString * params =#"name=Ravi&loc=India&age=31&submit=true";
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Response:%# %#\n", response, error);
if(error == nil)
{
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Data = %#",text);
}
}];
[dataTask resume];
}
Here is good tutorial for NSURLSession.
Related
I want to get data from different url in same thread. How can i do that.
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:defaultConfigObject];
for(int i=0;i<[url count];i++){
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url[i]];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[param dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask * dataTask =[session dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Response:%# %#\n", response, error);
if(error != nil)
{
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Data = %#",text);
}
else{
// If no error occurs, check the HTTP status code.
NSInteger HTTPStatusCode = [(NSHTTPURLResponse *)response statusCode];
// If it's other than 200, then show it on the console.
if (HTTPStatusCode != 200) {
NSLog(#"HTTP status code = %d", HTTPStatusCode);
}
// Call the completion handler with the returned data on the main thread.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
completionHandler(data);
}];
}
}];
[dataTask resume];
}
i want to get all value ..If i get that than i will save each url value in an array
I'm trying to send a simple POST request in iOS to test a server I've written. My code is as follows:
NSURL *url = [NSURL URLWithString:#"http://localhost:8888/createUser"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
NSString *bodyData = #"username=johndoe";
request.HTTPMethod = #"POST";
request.HTTPBody = [bodyData dataUsingEncoding:NSUTF8StringEncoding];
request.timeoutInterval = 5;
[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"resp: %#, err: %#", response, error);
}];
I've been trying to debug this and am very confused. The code inside the completionHandler is never run, the request never times out, and the server never sees anything. Nothing seems to happen at all. I was able to do a request using the old NSURLConnection but would like to avoid that since it's deprecated. What's the issue here?
In addition to setting up your code to do a GET when you say you want to do a POST (as josemando points out in his comment), you're not starting your task.
You need to change your last line like this:
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest: request
completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error)
{
NSLog(#"resp: %#, err: %#", response, error);
}];
[task resume];
(dataTaskWithRequest creates and returns a data task object which you then have to submit for execution with the resume method.)
Since ASIHTTPRequest is deprecated, I am migrating my code to use NSURLSession based server communication. Currently, I am using NSDictionary "userInfo" property of ASIHTTPRequest to send additional user information. The description of "userInfo" in ASIHTTPRequest documentation is "Custom user information associated with the request (not sent to the server)".
After the request is processed, I re-fetch this "userInfo" object from the request object and take action accordingly.
My ASIHTTPRequest code example is
Request:
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:#"http://www.google.com"];
[request setDelegate:self];
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:1] forKey:#"requestCount"];
[request setUserInfo:userInfo];
I want to achieve same functionality through NSURLSession, how can I do this?
NSURLSession code example:
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: self.queue];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest setTimeoutInterval:timeOutSeconds];
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Response:%# %#\n", response, error);
if(error == nil)
{
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Data = %#",text);
}
}
}];
[dataTask resume];
Because you are using completion handler so you can use block variable, try below code:
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: self.queue];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest setTimeoutInterval:timeOutSeconds];
__block NSDictionary *userInfo = [NSDictionary dictionaryWithObjectAndKeys...........];**
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Response:%# %#\n", response, error);
NSLog(#"UserInfo: %#", userInfo);
if(error == nil)
{
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Data = %#",text);
}
}
}];
[dataTask resume];
The cleanest way to go is to subclass NSURLRequest and add the desired property - be it tag, or userInfo and use it the same way you are used to with ASIHTTP framework.
I am trying to make a simple class that I can use to call a post web service.
Everything is working perfectly except that I am not able to return the NSData.
This is my code:
+ (NSData *)postCall:(NSDictionary *)parameters fromURL:(NSString *)url{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSMutableArray *pairs = [[NSMutableArray alloc]init];
for(NSString *key in parameters){
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, parameters[key]]];
}
NSString *requestParameters = [pairs componentsJoinedByString:#"$"];
NSURL *nsurl = [NSURL URLWithString:url];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:nsurl];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[requestParameters dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//return data;
}];
[dataTask resume];
return nil;
}
Please notice that I have //return data but it gives me this error
Incompatible block pointer types sending 'NSData *(^)(NSData *__strong, NSURLResponse *__strong, NSError *__strong)' to parameter of type 'void (^)(NSData *__strong, NSURLResponse *__strong, NSError *__strong)'
My question is:
Is my way good or it will cause me problems in the future? I don't have image to download and I don't have anything to upload, I just have to send simple string data and receive simpe string data. Or it will be better to but that code in each function independently?
How can I return the data please?
You cannot just return the data (because the NSURLSessionDataTask runs asynchronously). You probably want to employ your own completion block pattern, similar to the completionHandler of the dataTaskWithRequest method.
So, you would add your own block parameter to your method, that you'll invoke from inside the dataTaskWithRequest method's completionHandler:
+ (NSURLSessionDataTask *)postCall:(NSDictionary *)parameters fromURL:(NSString *)url completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler {
// create your request here ...
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (completionHandler)
completionHandler(data, response, error);
}];
[dataTask resume];
return dataTask;
}
Or, because this dataTaskWithRequest runs on a background thread, it’s sometimes useful to make sure to dispatch the completion handler back to the main queue, e.g.
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (completionHandler)
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(data, response, error);
});
}];
Note, as an aside, I think it's good to return the NSURLSessionDataTask reference, like above, so (a) the caller can make sure the data task was successfully created; and (b) you have the NSURLSessionTask reference that you can use to cancel the task in case, at some future date, you want to be able to cancel the request for some reason (e.g. the user dismisses the view controller from which the request was issued).
Anyway, you'd then invoke this with:
NSURLSessionTask *task = [MyClass postCall:parameters fromURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// put whatever code you want to perform when the asynchronous data task completes
}];
if (!task) {
// handle failure to create task any way you want
}
You ask:
Is my way good or it will cause me problems in the future? I don't have [an] image to download and I don't have anything to upload, I just have to send [some] simple string data and receive [simple] string data. Or it will be better to but that code in each function independently?
If you're receiving simple string data back, I'd suggest composing your response in JSON format, and then having the completion block in postCall use NSJSONSerialization to extract the response. Using JSON makes it easier for the app to differentiate between successful response and a variety of server related problems that might also return string responses.
So, let's say you modified your server code to return a response like so:
{"response":"some text"}
Then you could modify postCall to parse that response like so:
+ (NSURLSessionDataTask *)postCall:(NSDictionary *)parameters fromURL:(NSString *)url completionHandler:(void (^)(NSString *responseString, NSError *error))completionHandler {
// create your request here ...
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (completionHandler) {
if (error) {
completionHandler(nil, error);
} else {
NSError *parseError = nil;
NSDictionary *responseDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
completionHandler(responseDictionary[#"response"], parseError);
}
}
}];
[dataTask resume];
return dataTask;
}
In terms of your underlying question, whether a method like postCall makes sense, yes, I think it makes perfect sense to put the details of creating the request in a single method. My minor reservation in your implementation was your decision to make it a class method rather than an instance method. You're currently creating a new NSURLSession for each request. I'd suggest making postCall an instance method (of a singleton if you want) and then saving the session as a class property, which you set once and then re-use on subsequent queries.
You should use a block method.
First define a block
typedef void (^OnComplete) (NSData *data);
Use the following method
+ (void)postCall:(NSDictionary *)parameters fromURL:(NSString *)url withBlock:(OnComplete)block; {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSMutableArray *pairs = [[NSMutableArray alloc]init];
for(NSString *key in parameters){
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, parameters[key]]];
}
NSString *requestParameters = [pairs componentsJoinedByString:#"&"];
NSURL *myURL = [NSURL URLWithString:url];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:myURL];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[requestParameters dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
block(data);
}];
[dataTask resume];
}
I am a beginner in handling connections in iOS. I want to perform a POST request and a GET request to a url. The thing is that I need to first do the POST so I can get an access token parsed in a JSON that later will place in the header of the GET request, so I can retrieve the login data I need. I am using custom delegate methods, but when I run this, the GET request is performed earlier than the POST, so I cannot get the key before the GET is done. Is there any way to perform the POST first and then the GET? Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//params
self.user = #"carlos";
self.pass = #"1234";
self.grantType = #"password";
self.clientId = #"7da713e69afc96cf894e";
self.clientSecret = #"2c14ec54cfdfd6faec4ef56ca7f0870ab83f820b";
// Set the side bar button action. When it's tapped, it'll show up the sidebar.
self.sideBarButton.target = self.revealViewController;
self.sideBarButton.action = #selector(revealToggle:);
// Set the gesture
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
[self httpPostWithCustomDelegate];
[self sendHTTPGet];
}
-(void) httpPostWithCustomDelegate
{
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSURL * url = [NSURL URLWithString:#"http://192.237.241.175:8090/oauth2/access_token/"];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
NSString * params =[NSString stringWithFormat:#"&username=%#&password=%#&grant_type=%#&client_id=%#&client_secret=%#&scope=write",self.user,self.pass,self.grantType,self.clientId,self.clientSecret];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Retrieving Data");
// NSLog(#"Response:%# %#\n", response, error);
if(error == nil)
{
//Test Display
// NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
// NSLog(#"Data = %#",text);
self.responseDataPost = [[NSMutableData alloc] init];
[self.responseDataPost appendData:data];
NSError *error;
NSMutableDictionary *receivedData = [NSJSONSerialization JSONObjectWithData:self.responseDataPost options:NSJSONReadingMutableContainers error:&error];
//obtener del dictionary el access token y meter como header Authorization Bearer + id
//NSArray *accessToken = [receivedData valueForKeyPath:#"access_token"];
self.connTxtPost.text = [receivedData valueForKeyPath:#"access_token"];
self.recData = [receivedData valueForKeyPath:#"access_token"];
}
}];
[dataTask resume];
}
-(void) sendHTTPGet
{
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
NSURL * url = [NSURL URLWithString:#"http://192.237.241.175:8090/snippets/"];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest setHTTPMethod:#"GET"];
self.connTxtGet.text = self.recData ;
[urlRequest setValue:self.recData forHTTPHeaderField:#"Authorization"];
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//NSLog(#"Response:%# %#\n", response, error);
if(error == nil)
{
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Data = %#",text);
}
}];
[dataTask resume];
}
You don't need to call these two method all together.
[self httpPostWithCustomDelegate];
[self sendHTTPGet];
You are making two request at the same time. But which response will come first you can not determine it. As you need data from "POST" request to make the "GET" request. Do something like this:
-(void) httpPostWithCustomDelegate{
.............
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
...................
...................
self.connTxtPost.text = [receivedData valueForKeyPath:#"access_token"];
self.recData = [receivedData valueForKeyPath:#"access_token"];
[self sendHTTPGet];
}
}];
[dataTask resume];
}
This will make sure when you make the "GET" request you have the "access_token". Hope this will work just fine. :)