Get NSURLSessionDataTask In Intercepted Completion Handler - ios

I'm using AFNetworking and I am overridding -dataTaskWithRequest:completionHandler: to basically MITM my request and do some error handling prior to calling the actual response block. However, I need access to the NSURLSessionDataTask object that the -dataTaskWithRequest:completionHandler: method creates, inside my intercepted completion handler. So my override of the method looks like this:
-(NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLResponse *, id, NSError *))completionHandler {
void (^interceptedCompletionHandler)(NSURLResponse *, id, NSError *) = ^void(NSURLResponse * response, id responseObject, NSError * error) {
if (error) {
// Do custom stuff here that needs to use task.taskIdentifier
}
// Then call the original completion handler
completionHandler(response, responseObject, error);
}
return [super dataTaskWithRequest:request completionHandler:interceptedCompletionHandler];
}
Is this possible?
I know that AFNetworking could add this quite easily as the response parameter of the completion handler is set as task.response in AFURLSessionManager, which is the class that contains the -URLSession:task:didCompleteWithError: that calls the completion handler.

I've figured it out. By declaring the block inline and assigning the super call to a variable, I'm able to use the resulting task object:
-(NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLResponse *, id, NSError *))completionHandler {
__block NSURLSessionDataTask* task = [super dataTaskWithRequest:request completionHandler:
^void(NSURLResponse * response, id responseObject, NSError * error)
{
// I can use task here
if (error) {
// Do custom stuff here that needs to use task.taskIdentifier
}
// Then call the original completion handler
completionHandler(response, responseObject, error);
}
return task;
}

Related

Block with completion and arguments

I am new to blocks. I am trying to analyze how the following code works.
As i understand this specific method has a block and returns a
NSURLSessionDataTask
. getTotalFollowersFrom is the name of the method. (NSString*)influencerId is the userId that is passed when calling this method. WithCompletion: is used so we know when the method completes the Api call. Void is what the block returns. The caret symbol (^) is used to define the block. The following (id _Nullable likesCountRequestResponse, NSError * _Nullable error)completion are arguments. This is what i do not understand. According to documentation arguments have return values inside the block. I see the return result; that returns the NSURLSessionDataTask but i do not understand how the values return for the arguments of the block. What am i missing? Could anyone please explain to me?
- (NSURLSessionDataTask *)getTotalLikesFrom:(NSString*)userId withCompletion:(void (^)(id _Nullable likesCountRequestResponse, NSError * _Nullable error))completion {
NSString* postString = [NSString stringWithFormat:#"apicall/%#", userId];
#weakify(self)
NSURLSessionDataTask *result = [self GET:postString parameters:nil completion:^(OVCResponse * _Nullable response, NSError * _Nullable error) {
#strongify(self)
[self handleResponse:response error:error adjustErrorBlock:self.commonValidationErrorAdjustBlock completion:completion];
}];
return result;
}
- (void)handleResponse:(nullable OVCResponse *)response error:(nullable NSError *)error adjustErrorBlock:(nullable ApiClientv2AdjustErrorBlock)adjustErrorBlock completion:(void (^)(id _Nullable result, NSError * _Nullable error))completion
{
if (response.HTTPResponse.statusCode >= 500)
{
error = nil != error ? error : NSError.com_eight_APIServiceUnknownError; [SentrySDK captureError:error];
}
else
{
error = nil != error ? error : NSError.com_eight_APIServiceUnknownError;
}
id result = nil == error ? response.result : nil;
completion(result, error);
}
Your example is not complete. So, let's consider a MCVE:
- (NSURLSessionDataTask *)networkRequestWithCompletion:(void (^)(NSDictionary * _Nullable object, NSError * _Nullable error))completion {
NSURL *url = [NSURL URLWithString:#"https://httpbin.org/get"];
NSURLSessionDataTask *task = [NSURLSession.sharedSession dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!data || error) {
completion(nil, error);
return;
}
NSError *parseError;
NSDictionary *resultObject = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&parseError];
if (parseError) {
completion(nil, parseError);
return;
}
completion(resultObject, nil);
}];
[task resume];
return task;
}
This performs a network request and parses the JSON. Inside that method, you will see references to:
completion(nil, error);
Or:
completion(resultObject, nil);
That is how the data is passed back to the caller. The method supplies the parameters of the block when it calls completion . Thus, this method can be supplied a block and use these two parameters:
[self networkRequestWithCompletion:^(NSDictionary *dictionary, NSError *error) {
if (error) {
NSLog(#"error = %#", error);
return;
}
// you can access the `dictionary` parameter here ...
NSLog(#"dictionary = %#", dictionary);
}];
// ... but you cannot reference the `dictionary` or `error` parameters here
// because the above runs asynchronously (i.e., later).
In the code snippet you supplied in your question, you are not calling completion anywhere. But notice that it is supplied to handleResponse. That method is undoubtedly calling the block for you.
As a matter of personal preference, I think that the choice of result for the NSURLSessionDataTask is a little confusing. So in my example, I gave it a better name, task, to make it clear that it is the NSURLSessionTask object, not the result of the network request.
But what is the purpose of this object? It is so that the caller has the option to cancel the request if it wants. For example, you might have:
#interface ViewController ()
#property (nonatomic, weak) NSURLSessionTask *task;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.task = [self networkRequestWithCompletion:^(NSDictionary *dictionary, NSError *error) {
if (error) {
NSLog(#"error = %#", error);
return;
}
NSLog(#"dictionary = %#", dictionary);
}];
}
- (IBAction)didTapCancelButton:(id)sender {
[self.task cancel];
}
...
#end
So, the NSURLSessionTask reference is returned, which we save, so that we can have, for example, a cancel button that will cancel that asynchronous network request.
But, in short, do not conflate the NSURLSessionTask reference that is immediately returned by networkRequestWithCompletion (to give us the option to cancel the request later) with the parameters of the block, which we use to supply the caller with the results of the network request.

Wait response for HTTP request using NSURLSession - Objective C

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);
}];

How to wait for finish dataTaskWithRequest?

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
});

How to get server response data in NSURLSession without completion block

I am using NSURLSession for background image uploading. And according to uploaded image my server gives me response and I do change in my app accordingly. But I can't get my server response when my app uploading image in background because there is no completion block.
Is there way to get response without using completion block in NSURLUploadTask?
Here is my code :
self.uploadTask = [self.session uploadTaskWithRequest:request fromData:body completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *returnString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"returnString : %#",returnString);
NSLog(#"error : %#",error);
}];
[self.uploadTask resume];
But i got this error..
Terminating app due to uncaught exception 'NSGenericException', reason: 'Completion handler blocks are not supported in background sessions. Use a delegate instead.'
But if I can't use completion handler than how should I get the server response. It says use delegate but I can't find any delegate method which can gives me server response.
A couple of thoughts:
First, instantiate your session with a delegate, because background sessions must have a delegate:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:kSessionIdentifier];
self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
Second, instantiate your NSURLSessionUploadTask without a completion handler, because tasks added to a background session cannot use completion blocks. Also note, I'm using a file URL rather than a NSData:
NSURLSessionTask *task = [self.session uploadTaskWithRequest:request fromFile:fileURL];
[task resume];
Third, implement the relevant delegate methods. At a minimum, that might look like:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
NSMutableData *responseData = self.responsesData[#(dataTask.taskIdentifier)];
if (!responseData) {
responseData = [NSMutableData dataWithData:data];
self.responsesData[#(dataTask.taskIdentifier)] = responseData;
} else {
[responseData appendData:data];
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error) {
NSLog(#"%# failed: %#", task.originalRequest.URL, error);
}
NSMutableData *responseData = self.responsesData[#(task.taskIdentifier)];
if (responseData) {
// my response is JSON; I don't know what yours is, though this handles both
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil];
if (response) {
NSLog(#"response = %#", response);
} else {
NSLog(#"responseData = %#", [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]);
}
[self.responsesData removeObjectForKey:#(task.taskIdentifier)];
} else {
NSLog(#"responseData is nil");
}
}
Note, the above is taking advantage of a previously instantiated NSMutableDictionary called responsesData (because, much to my chagrin, these "task" delegate methods are done at the "session" level).
Finally, you want to make sure to define a property to store the completionHandler provided by handleEventsForBackgroundURLSession:
#property (nonatomic, copy) void (^backgroundSessionCompletionHandler)(void);
And obviously, have your app delegate respond to handleEventsForBackgroundURLSession, saving the completionHandler, which will be used below in the URLSessionDidFinishEventsForBackgroundURLSession method.
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
// This instantiates the `NSURLSession` and saves the completionHandler.
// I happen to be doing this in my session manager, but you can do this any
// way you want.
[SessionManager sharedManager].backgroundSessionCompletionHandler = completionHandler;
}
And then make sure your NSURLSessionDelegate calls this handler on the main thread when the background session is done:
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
if (self.backgroundSessionCompletionHandler) {
dispatch_async(dispatch_get_main_queue(), ^{
self.backgroundSessionCompletionHandler();
self.backgroundSessionCompletionHandler = nil;
});
}
}
This is only called if some of the uploads finished in the background.
There are a few moving parts, as you can see, but that's basically what's entailed.

How to make one function that organizes multiple NSURLSessionDataTask (s)? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
After creating an NSURLSession, it appears that making multiple web queries with NSURLSessionDataTask can lead to some code writing redundancy, which I'd like to clean up. The following code is repeated multiple times:
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
}];
[task resume];
Could the task be initiated from inside a dedicated function? It would be easy to pass in the NSMutableURLRequest as a variable but what about the completion handler?
Theoretically you could pass in the completion handler as a block, right? The problem I ran into is that the callback variables (data & response) can't be referred to from outside the provided completion handler, so how can you write a block that refers to them?
Right now I have 3 or 4 separately written tasks going, and it looks like it may stay that way unless someone has an idea!
EDIT, SOLUTION:
The method definition is:
- (void) engage:(NSMutableURLRequest *)request with:(void (^)(NSData *, NSURLResponse *, NSError *))yourmom;
& The block literal that it takes looks like:
void (^yourmom)(NSData *, NSURLResponse *, NSError *) = ^(NSData *data, NSURLResponse *response, NSError *error) {
// stuff
};
Basically, the key thing I was missing was that I needed my block to accept variables so that I could refer to them by name without compiler errors. The above code creates a variable-accepting block which lets me get around the compiler warnings about out of scope variables. Also if the code
[self engage:request with:yourmom];
shows up somewhere we'll know where it came from.
If you're asking how can you pass the completion block to the utility method, you should just supply a block parameter to your method:
- (NSURLSessionDataTask *)startDataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))block
{
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:block];
[task resume];
return task;
}
Or, if there are portions of the completion block which you're repeating all the time, then go ahead and put that in your utility method, but then invoke the caller's block:
- (NSURLSessionDataTask *)startDataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))block
{
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
// do here stuff that I'll always do, such as logging errors, checking response codes, etc.
// when that's done, I can now invoke the caller's block
if (block) {
block(data, response, error);
}
}];
[task resume];
return task;
}
I'd say make a category on NSURLSession and do the following:
First typedef a completion block in the header file:
typedef void (^NSURLSessionDataTaskCompletionHandler) (NSData *data, NSURLResponse *response, NSError *error);
Then add a method to the header file:
- (NSURLSessionDataTask *)startTaskWithRequest:(NSURLRequest *)request completionHandler:(NSURLSessionDataTaskCompletionHandler)completionHandler;
Finally the body of the function in the implementation file:
- (NSURLSessionDataTask *)startTaskWithRequest:(NSURLRequest *)request completionHandler:(NSURLsessionDataTaskCompletionHandler)completionHandler
{
NSURLSessionDataTask *task = [self dataTaskWithRequest:request completionHandler:completionHandler];
[task resume];
return task;
}
Now you get to very easily do something like:
[session startTaskWithRequest:request completionHandler:(NSData *data, NSURLResponse *response, NSError *error){
// do stuff
}];

Resources