Warning:'sendSynchronousRequest(_:returningResponse:)' was deprecated
in iOS 9.0: Use [NSURLSession dataTaskWithRequest:completionHandler:]
(see NSURLSession)
urlData = try NSURLConnection.sendSynchronousRequest(request, returningResponse:&response)
Any idea on how to get rid of this warning? I just upgraded from Swift 1.2 to Swift 2
UPDATE: Fonix is marked as my best answer. If your trying to add a try statement, I modified his answer as followed:
urlData = try NSURLSession.dataTaskWithRequest(<#request: NSURLRequest#>, completionHandler: <#((NSData!, NSURLResponse!, NSError!) -> Void)?##(NSData!, NSURLResponse!, NSError!) -> Void#>)
Use NSURLSession instead like below,
For Objective-C
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:londonWeatherUrl]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
// handle response
}] resume];
For Swift,
var request = NSMutableURLRequest(URL: NSURL(string: "YOUR URL"))
var session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
var params = ["username":"username", "password":"password"] as Dictionary<String, String>
var err: NSError?
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(params, options: nil, error: &err)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
println("Response: \(response)")})
task.resume()
I wrote the following solution for the cases when you actually need for synchronous request which blocks the current thread execution. I use this code for migration from NSURLConnection to NSURLSession in the complex solution where it was quite a hassle to just change to async approach. With this solution the migration is just method name replacement.
NOTE: If you have a simple case, please use the accepted answer instead.
- (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
{
NSError __block *err = NULL;
NSData __block *data;
BOOL __block reqProcessed = false;
NSURLResponse __block *resp;
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable _data, NSURLResponse * _Nullable _response, NSError * _Nullable _error) {
resp = _response;
err = _error;
data = _data;
reqProcessed = true;
}] resume];
while (!reqProcessed) {
[NSThread sleepForTimeInterval:0.02];
}
if (response != nil)
*response = resp;
if (error != nil)
*error = err;
return data;
}
Usage (simple replace NSURLConnection to this method):
//NSData *data = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:&resp error:&err];
NSData *data = [self sendSynchronousRequest:theRequest returningResponse:&resp error:&err];
If you need to block the current thread (like Mike Keskinov's answer), best to use gdc semaphore instead of doing a [NSThread sleepForTimeInterval:0]. e.g.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:londonWeatherUrl]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
// handle response
dispatch_semaphore_signal(semaphore);
}] resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
and Swift (tested on 5.0):
let semaphore = DispatchSemaphore(value:0)
URLSession.shared.dataTask(with: serverUrl) { (httpData, response, error) in
// handle response
semaphore.signal()
}.resume()
semaphore.wait()
I have modified the code of Nilesh Patel a little bit, so you can use the old call, just by changing class name.
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error {
__block NSData *blockData = nil;
#try {
__block NSURLResponse *blockResponse = nil;
__block NSError *blockError = nil;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable subData, NSURLResponse * _Nullable subResponse, NSError * _Nullable subError) {
blockData = subData;
blockError = subError;
blockResponse = subResponse;
dispatch_group_leave(group);
}] resume];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
*error = blockError;
*response = blockResponse;
} #catch (NSException *exception) {
NSLog(#"%#", exception.description);
} #finally {
return blockData;
}
}
Swift 4 / Xcode 9
If you really want the request to be synchronous like in the deprecated semantics, you can block the main thread with an empty loop on a condition set true by the completion handler:
let request = URLRequest(url: URL(string: "YOUR_URL")!)
let session = URLSession.shared
var gotResp = false
let task = session.dataTask(with: request,
completionHandler: { data, response, error -> Void in
// do my thing...
gotResp = true
})
task.resume()
// block thread until completion handler is called
while !gotResp {
// wait
}
print("Got response in main thread")
...
EDIT: or if you prefer to use semaphores like in the Obj-C Nick H247 answer:
let request = URLRequest(url: URL(string: "YOUR_URL")!)
let session = URLSession.shared
let ds = DispatchSemaphore( value: 0 )
let task = session.dataTask(with: request,
completionHandler: { data, response, error -> Void in
// do my thing..., then unblock main thread
ds.signal()
})
task.resume()
// block thread until semaphore is signaled
ds.wait()
print("Got response in main thread")
...
Here is a complete version of mine with dispatch_semaphore_t and return response and error without block assign warning . Thank #Nick H247 and #Mike Keskinov.
- (NSData*)sendSynchronousRequest:NSURLRequest *urlRequest
returningResponse:(NSURLResponse **)outResponse
error:(NSError **)outError
{
NSError __block *err = NULL;
NSData __block *data;
BOOL __block reqProcessed = false;
NSURLResponse __block *resp;
// data = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:response error:error];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSession *session = _session;
[[session dataTaskWithRequest:urlRequest
completionHandler:^(NSData *_data,
NSURLResponse *_response,
NSError *_error) {
// handle response
data = _data;
resp = _response;
err = _error;
reqProcessed = true;
dispatch_semaphore_signal(semaphore);
}] resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (reqProcessed) {
if(outResponse != NULL) {
*outResponse = resp;
}
if (outError != NULL) {
*outError = err;
}
}
return data;
}
you can hide that warning in your project by using following code write your method between input directives and your warning goes away.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
- (void)yourMethodToCallNSURLConnection {
//use deprecated stuff
}
#pragma GCC diagnostic pop
Related
I handle some old code, it runs well, but now crash only on ios 14
here is the demo
static NSData *DownloadWithRange(NSURL *URL, NSError *__autoreleasing *error) {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
request.timeoutInterval = 10.0;
__block NSData *data = nil;
__block dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSURLSessionConfiguration *config = NSURLSessionConfiguration.ephemeralSessionConfiguration;
NSURLSession *URLSession = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask *task = [URLSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable taskData, NSURLResponse * _Nullable response, NSError * _Nullable taskError) {
data = taskData;
if (error)
*error = taskError;
dispatch_semaphore_signal(sema);
}];
[task resume];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return data;
}
- (IBAction)crashButton:(id)sender {
NSURL *url = [NSURL URLWithString:#"http://error"];
NSError * error = nil;
NSData *compressedData = DownloadWithRange(url, &error);
NSLog(#"error is %#",error);
}
before DownloadWithRange returned, the taskError memory(NSURLError) has released
on ios 13, it don't crash
it's really weird
The zombie diagnostics are letting you know that the autorelease object is getting deallocated by the time the data is returned. You should not be instantiating an autorelease object in one thread and trying to have a pool on a separate thread manage that. As the docs say:
Autorelease pools are tied to the current thread and scope by their nature.
While the problem might be manifesting itself differently in iOS 14, I do not believe that this pattern was ever acceptable/prudent.
If you're going to use this pattern (which I wouldn't advise; see below), you can solve this problem by copying the error object on the calling thread before returning:
static NSData *DownloadWithRange(NSURL *URL, NSError * __autoreleasing *error) {
...
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
if (error) {
*error = [*error copy];
}
return data;
}
FWIW, this technique of using semaphore to make asynchronous method behave synchronously is generally considered an anti-pattern. And you definitely should never use this pattern from the main thread.
I would suggest adopting asynchronous patterns:
- (NSURLSessionTask *)dataTaskWithURL:(NSURL *)url completion:(void (^ _Nonnull)(NSData * _Nullable data, NSError * _Nullable error))completion {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.timeoutInterval = 10.0;
NSURLSessionConfiguration *config = NSURLSessionConfiguration.ephemeralSessionConfiguration;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(data, error);
});
}];
[task resume];
[session finishTasksAndInvalidate];
return task;
}
And
[self dataTaskWithURL:url completion:^(NSData * _Nullable data, NSError * _Nullable error) {
// use `data` and `error` here
}];
// but not here
Note, in addition to adopting asynchronous completion block pattern, a few other observations:
If you’re going to create a new NSURLSession for each request, make sure to invalidate it or else you will leak memory.
I’m returning the NSURLSessionTask, which some callers may want in case they might want to cancel the request (e.g. if the view in question is dismissed or a new request must be generated). But as shown above, you don’t need to use this NSURLSessionTask reference if you don’t want.
I'm dispatching the completion handler back to the main queue. That is not strictly necessary, but it is often a useful convenience.
Right now I am developing I little class that has a method for sending a POST request. This method is intended for returning a ResponseModel (which basically has two ivars: code, message), this model is going to be map from response.
I am using dataTaskWithRequest:urlRequest completionHandler: method. Like this:
+ (void)sendPOSTRequest1:(id)data withResponse:(void (^) (ResponseModel * data) )taskResponse {
NSError *error = nil;
NSMutableURLRequest * urlRequest = [self getRequestObject];
[urlRequest setHTTPMethod:#"POST"];
NSData * requestData = [self encodeAndEncrypt:data];
[urlRequest setHTTPBody:requestData];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session
dataTaskWithRequest:urlRequest
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
ResponseModel * responseModel = [NSKeyedUnarchiver
unarchivedObjectOfClass:[ResponseModel class]
fromData:data
error:&error];
taskResponse(responseModel);
}];
[dataTask resume];
}
And call the method this way:
DummyModel * dummy = [[DummyModel alloc] init];
__block ResponseModel * result = [[ResponseModel alloc] init];
[HTTPRequest sendPOSTRequest1:dummy withResponse:^(ResponseModel *data) {
result = data;
NSLog(#"data %#",data);
}];
// It`s not sure that the asyncronous request has already finished by this point
NSLog(#"POST result : %#",result);
My problem is that I do not want to execute a code in call back block because I need to wait for the response in order to return a ResponseModel and whoever is implementing this can receive the Model and make other stuff.
I been researching for using NSURLConnection because it has a method for executing Synchronous request, but now It´s deprecated, so I been wondering: is It a way I can wait for a response using what I have in the code ?
You can use GCD to implement synchronous request like this:
swift code
public static func requestSynchronousData(request: URLRequest) -> Data? {
var data: Data? = nil
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
let task = URLSession.shared.dataTask(with: request, completionHandler: {
taskData, _, error -> () in
data = taskData
if data == nil, let error = error {print(error)}
semaphore.signal()
})
task.resume()
_ = semaphore.wait(timeout: .distantFuture)
return data
}
Objective-C code
+ (NSData *)requestSynchronousData:(NSURLRequest *)request {
__block NSData * data = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable taskData, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(#"%#", error);
}
data = taskData;
dispatch_semaphore_signal(semaphore);
}];
[task resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return data;
}
You can use dispatch_async to handle UI interaction inside the block
DummyModel * dummy = [[DummyModel alloc] init];
__block ResponseModel * result = [[ResponseModel alloc] init];
[HTTPRequest sendPOSTRequest1:dummy withResponse:^(ResponseModel *data) {
result = data;
dispatch_async(dispatch_get_main_queue(), ^{
// handle some ui interaction
});
NSLog(#"data %#",data);
}];
I have a method, the method have return the nsdata value, but I don't known how to get the return value from NSURLSessionDataTask block. and how to call the getDownloadFileData methods.Code for task is :-
caller:
NSData *getFileDataResult = [self getDownloadFileData:pathString];
method:
- (NSData*) getDownloadFileData : (NSString*) filePath {
NSURLSessionDataTask *downloadFile = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:filePath] completionHandler:^(NSData *fileData, NSURLResponse *response, NSError *error){
// .....
// fileData should return out.
[downloadFile resume];
});
// I want to return the fileData after download completion.
// how to return?
}
Have anyone can give me a hand?
Thank you very much.
Please check my answer, I hope this helpful
- (NSData *)getDownloadFileData:(NSString *)filePath {
__block NSData *responseData = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSURLSessionDataTask *downloadFile = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:filePath] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
responseData = data;
dispatch_semaphore_signal(sema);
}];
[downloadFile resume];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return responseData;
}
- (void)whereToCall {
// Because to prevent the semaphore blocks main thread
dispatch_queue_t myQueue = dispatch_queue_create("com.abc", 0);
dispatch_async(myQueue, ^{
NSData *data = [self getDownloadFileData:#"urlString"];
});
}
- (void)betterGetDownloadFileData:(NSString *)filePath completion:(void (^)(NSData * __nullable data))completion {
NSURLSessionDataTask *downloadFile = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:filePath] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (completion) {
completion(data);
}
}];
[downloadFile resume];
}
I recommend you should design your code as my suggestion that using block instead.
First of all you have put resume method at wrong place. It should be like this:
- (NSData*) getDownloadFileData : (NSString*) filePath {
NSURLSessionDataTask *downloadFile = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:filePath] completionHandler:^(NSData *fileData, NSURLResponse *response, NSError *error){
// .....
// fileData should return out.
});
[downloadFile resume];//NOTICE THE PLACEMENT HERE
// I want to return the fileData after download completion.
// how to return?
}
Second thing is , you can simply create a NSData variable and assign it the value in completion block rather than passing data back.
OR
Simply do like this in completion block
if(fileData){
return fileData;
}
I had iOS framework which it send JSON to server using NSURLSessionDataTask like this :
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
int responseStatusCode = [httpResponse statusCode];
if (responseStatusCode == 200)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate onJsonHttpResult:data andStatusResponse:responseStatusCode];
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate onJsonHttpResult:nil andStatusResponse:responseStatusCode];
});
}
}];
[postDataTask resume];
but whenever I run it, [self.delegate onJsonHttpResult:nil andStatusResponse:responseStatusCode]; not called.
is there any way to get value outside NSURLSessionDataTask when it run inside the framework ?
Thanks
My suggestion is to create and use APIHelperClass with completionBlock.
That will be more easy and affective then use of custom Delegate as per my view.
To create it you can do as follow:
In APIHelperClass.h
#import <Foundation/Foundation.h>
#interface APIHelperClass : NSObject
+(void)apiCallSharedSessionPOST:(NSURLRequest *)request withCompletionHandlar:(void (^) (NSDictionary *dicResult,NSError *error, int status))completionBlock;
#end
And
APIHelperClass.m
#import "APIHelperClass.h"
#implementation APIHelperClass
+(void)apiCallSharedSessionPOST:(NSURLRequest *)request withCompletionHandlar:(void (^) (NSDictionary *dicResult,NSError *error, int status))completionBlock;
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
int responseStatusCode = (int)[httpResponse statusCode];
if (error!=nil)
{
completionBlock(nil,error,responseStatusCode);
[task suspend];
}
else
{
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
completionBlock(dic,error,responseStatusCode);
[task suspend];
}
}];
[task resume];
}
Then you can use that class for whole App and you don't need to create custom Delegates always.
Then Use that where you want Like :
NSURLRequest *request;
/*
Configure your Request Here
*/
[APIHelperClass apiCallSharedSessionPOST:request withCompletionHandlar:^(NSDictionary *dicResult, NSError *error, int status) {
}];
Thanks for help but I still need delegate to return my value outside framework.
I got issued that whenever I done with NSURLSessionDataTask, delegate is became null, I think it cause that delegate already released after I got response from NSURLSessionDataTask, So I tried to change #property delegate to strong and it work. I can return my value using delegate again. Thanks
How can I wait for finish of dataTaskWithRequest ? I need to perform some tasks after network fetch is completely over.
If you really need synchronous request, you can use semaphores.
I've implemented a small category on NSURLSession to provide this functionality.
In .h file:
#import Foundation.NSURLSession;
#interface NSURLSession (Additions)
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error;
#end
In .m file:
#implementation NSURLSession (Additions)
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse *__autoreleasing *)responsePointer error:(NSError *__autoreleasing *)errorPointer
{
dispatch_semaphore_t semaphore;
__block NSData *result = nil;
semaphore = dispatch_semaphore_create(0);
void (^completionHandler)(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error);
completionHandler = ^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error)
{
if ( errorPointer != NULL )
{
*errorPointer = error;
}
if ( responsePointer != NULL )
{
*responsePointer = response;
}
if ( error == nil )
{
result = data;
}
dispatch_semaphore_signal(semaphore);
};
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:completionHandler] resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return result;
}
#end
I think, that method is obvious inside the class
NSURLSessionDataTask *task = [defaultSession dataTaskWithRequest:request
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error)
{
// code after completion of task
}];
[task resume];
- (void) loginRequest:(NSString*) username withPassword:(NSString *) password callback:(void (^)(NSError *error, BOOL success))callback
{
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
if (error) {
// Handle error, optionally using
callback(error, NO);
}
else {
callback(nil, YES);
}
}];
[dataTask resume];
}
Call this method like so:
[self loginRequest:#"myUsername" password:#"password" callback:^(NSError *error, BOOL success) {
if (success) {
NSLog(#"My response back from the server after an unknown amount of time");
}
else {
NSLog(#"%#", error);
}
}];
lets just do your tasks in the completion block of dataTaskWithRequest.
Until than you can display an activity indicator to block the user from touching anything on the screen.
Example:
activityIndicator.startAnimating()
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
activityIndicator.stopAnimating()
// do your stuff here
});