AFNetworking 2.0.0-RC2 invokes failure block not on main thread - afnetworking

I got the AFNetworking version available on cocoapods (2.0.0-RC2) and in the AFURLSessionManager.m file the method do not invoke the failure block on the main thread in opposite of the success block. Is that on purpose or it is an error with the library?
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
success:(void (^)(NSURLResponse *response, id responseObject))success
failure:(void (^)(NSError *error))failure
{
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
if (failure) {
failure(error);
}
} else {
....
id responseObject = [self.responseSerializer responseObjectForResponse:response data:data error:&serializationError];
dispatch_async(dispatch_get_main_queue(), ^(void) {
if (serializationError) {
if (failure) {
failure(serializationError);
}
} else {
if (success) {
success(response, responseObject);
}
}
}

It seems that it was a bug, as in 2.0 final release the blocks are called on a different way, as you can see in the master branch.

Related

Nested NSURLSessionDataTask for two consecutive HTTP GETs

I am a beginner in Objective C and I am looking to do two consecutive HTTP GETs (one after the other). What I have got so far is that I have a NSURLSessionDataTask inside the completion block of the first NSURLSessionDataTask. This is causing my code to be a bit unreadable, so I was wondering what is a better way to do this?
Here is some sample code:
{
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSMutableURLRequest *url_request_1 = [NSMutableURLRequest requestWithURL:#"some_url_1"];
[url_request_1 setHTTPMethod:#"GET"];
NSURLSessionDataTask *url_task_1 = [session
dataTaskWithRequest:url_request_1
completionHandler:^(NSData *data1,
NSURLResponse *response1,
NSError *error1) {
if(data1 !=nil){
// Evaluate some_url_2 from the response of url_task_1
NSMutableURLRequest *url_request_2 = [NSMutableURLRequest requestWithURL:#"some_url_2"];
[url_request_2 setHTTPMethod:#"GET"];
NSURLSessionDataTask *url_task_2 = [session
dataTaskWithRequest:url_request_2
completionHandler:^(NSData *data2,
NSURLResponse *response2,
NSError *error2) {
if(data2 !=nil){
// Process data here
} else {
// Handle error here.
return;
}
}];
[urlRequest2 resume];
}
else{
// Handle error here
return;
}
}];
[url_task_1 resume];
}
This is made a little less unwieldy by changing your indentation style and using early-exit pattern.
- (void)performRequestsWithCompletion:(void (^ _Nonnull)(NSDictionary *, NSError *))completion {
NSMutableURLRequest *request1 = [NSMutableURLRequest requestWithURL:firstURL];
NSURLSessionDataTask *task1 = [self.session dataTaskWithRequest:request1 completionHandler:^(NSData *data1, NSURLResponse *response1, NSError *error1) {
if (!data1) {
// handle error here, then return
completion(nil, error1);
return;
}
NSMutableURLRequest *request2 = [NSMutableURLRequest requestWithURL:secondURL];
NSURLSessionDataTask *task2 = [self.session dataTaskWithRequest:request2 completionHandler:^(NSData *data2, NSURLResponse *response2, NSError *error2) {
if (!data2) {
// handle error here, then return
completion(nil, error2);
return;
}
// handle parsing `data2` here
NSDictionary *result = ...;
completion(result, nil);
}];
[task2 resume];
}];
[task1 resume];
}
Note, I added a completion handler here, because that's one of the best patterns to let whatever initiated this request that everything is done. Clearly, my block parameters assumed you would be returning a dictionary, so you should change this to be whatever type(s) your routine returns.
Alternatively (and especially if you're doing a lot more than just two successive web service calls), you can break this down into separate methods:
- (void)performRequestsWithCompletion:(void (^ _Nonnull)(NSDictionary *, NSError *))completion {
[self performFirstRequestWithCompletion:completion];
}
- (NSURLSessionTask *)performFirstRequestWithCompletion:(void (^ _Nonnull)(NSDictionary *, NSError *))completion {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:firstURL];
NSURLSessionTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!data || error) {
// Handle error here
completion(nil, error);
return;
}
// Evaluate some_url_2 from the response of url_task_1
[self performSecondRequestWithCompletion:completion];
}];
[task resume];
return task;
}
- (NSURLSessionTask *)performSecondRequestWithCompletion:(void (^ _Nonnull)(NSDictionary *, NSError *))completion {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:secondURL];
NSURLSessionTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!data || error) {
// Handle error here, and return
completion(nil, error);
return;
}
// Process data here
NSDictionary *result = ...;
completion(result, nil);
}];
[task resume];
return task;
}
With this pattern, no matter how many dependent calls you have, you won't end up with the tower of nested blocks.
In the interest of completeness, other patterns that avoid these towers of blocks-within-blocks include NSOperation patterns and third party approaches like futures/promises (e.g. PromiseKit). Those are beyond the scope of this question, but they're worth considering if you're doing this a lot.

Downloading files in serial order using NSURLConnection in iOS

I want to download 3 files in serial order. Two of them are txt files and one is .gz file. I am using NSURLConnection to download the above files.
I am very new to iOS Programming. I have seen in other question in SO and google that we can use serial dispatch queue to do some operation serially.
But I don't know how to do this with NSURLConnection. I tried below but did not work.
dispatch_queue_t serialQueue = dispatch_queue_create("com.clc.PropQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
[self downloadProp];
});
dispatch_async(serialQueue, ^{
[self downloadDatabase];
});
dispatch_async(serialQueue, ^{
[self downloadTxt];
});
Above code is not executing connectionDidFinishLoading of NSURLCOnnection. Anyone has Idea how to achieve this?
NSURLSession provides a queue that will download each task in the order in which they are created.
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionTask *task1 = [session dataTaskWithURL:[NSURL URLWithString:#"http://yahoo.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Complete 1");
}];
NSURLSessionTask *task2 = [session dataTaskWithURL:[NSURL URLWithString:#"http://msn.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Complete 2");
}];
NSURLSessionTask *task3 = [session dataTaskWithURL:[NSURL URLWithString:#"http://google.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Complete 3");
}];
// Regardless of which order the tasks are "resumed" (aka started) they will execute synchronously in the order added, above.
[task3 resume];
[task1 resume];
[task2 resume];
Update based on comments & chat:
To be more deterministic over the ordering & execution of tasks...
NSURLSession *session = [NSURLSession sharedSession];
__block NSURLSessionTask *task1 = nil;
__block NSURLSessionTask *task2 = nil;
__block NSURLSessionTask *task3 = nil;
task1 = [session dataTaskWithURL:urlToFirstFile completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// CHECK ERROR
NSLog(#"First file completed downloading");
[task2 resume];
}];
task2 = [session dataTaskWithURL:urlToSecondFile completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// CHECK ERROR
NSLog(#"Second file completed downloading");
[task3 resume];
}];
task3 = [session dataTaskWithURL:[NSURL URLWithString:#"http://google.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// CHECK ERROR
NSLog(#"Third file completed downloading");
}];
[task1 resume];
A simple recursive solution to ensure serial operation.
func serialisedRequests(session: URLSession, requests: [URLRequest], index: Int = 0) {
if index >= requests.count {
return
}
let task = session.dataTask(with: requests[index]) {
data, response, error in
serialisedRequests(session: session, requests: requests, index: index+1)
}
task.resume()
}
Simply set your NSURLSession's HTTPMaximumConnectionsPerHost property to 1 and add the tasks in the order you want.
See this answer for more details:
https://stackoverflow.com/a/21018964

How to return NSData from NSURLSessionDataTask completion handler

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

Re-run a failed NSURLSessionTask

I was wondering if it was possible to re-run a failed NSURLSessionDataTask. Here is the context in which I am encountering this problem.
I've got a web service object which uses AFNetworking 2.0 to handle the requests. In one of my methods, I've got this:
[HTTPSessionManager GET:path parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
} failure:^(NSURLSessionDataTask *task, NSError *error) {
//sometimes we fail here due to a 401 and have to re-authenticate.
}];
Now, sometimes the GET request fails because the user's auth token in my backend is . So what I'd like to do is run a re-authentication block to see if we can log in again. Something like this:
[HTTPSessionManager GET:path parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
} failure:^(NSURLSessionDataTask *task, NSError *error) {
if (task.response.statusCode == 401)
RunReAuthenticationBlockWithSuccess:^(BOOL success) {
//somehow re-run 'task'
} failure:^{}
}];
Is there any way to start the task over again?
Thanks!
If anyone is still interested, I ended up implementing my solution by subclassing HTTPSessionManager like so:
typedef void(^AuthenticationCallback)(NSString *updatedAuthToken, NSError *error);
typedef void(^AuthenticationBlock)(AuthenticationCallback);
#interface MYHTTPSessionManager : AFHTTPSessionManager
#property (nonatomic, copy) AuthenticationBlock authenticationBlock;
#end
#implementation MYHTTPSessionManager
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler {
void (^callback)(NSString *, NSError *) = ^(NSString *tokenString, NSError *error) {
if (tokenString) {
//ugh...the request here needs to be re-serialized. Can't think of another way to do this
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[mutableRequest addValue:AuthorizationHeaderValueWithTokenString(tokenString) forHTTPHeaderField:#"Authorization"];
NSURLSessionDataTask *task = [super dataTaskWithRequest:[mutableRequest copy] completionHandler:completionHandler];
[task resume];
} else {
completionHandler(nil, nil, error);
}
};
void (^reauthCompletion)(NSURLResponse *, id, NSError *) = ^(NSURLResponse *response, id responseObject, NSError *error){
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSInteger unauthenticated = 401;
if (httpResponse.statusCode == unauthenticated) {
if (self.authenticationBlock != NULL) {
self.authenticationBlock(callback); return;
}
}
completionHandler(response, responseObject, error);
};
return [super dataTaskWithRequest:request completionHandler:reauthCompletion];
}
#end

exctract code from handler to functions/methods obj C

I need to extract code from
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[self doSomethingWithData:data];
outstandingRequests--;
if (outstandingRequests == 0) {
[self doSomethingElse];
}
}];
to
void AsynchronousRequestCompletionHandler (NSURLResponse *response, NSData *data, NSError *error)
{
...
}
but I can't:
in xamarin iOS i did it: instead this
TextLogin.ShouldChangeCharacters += () => { some code...};
made this:
TextLogin.ShouldChangeCharacters += LoginAndPassValidator;
// ... some code
bool LoginAndPassValidator (UITextField textField, NSRange range, string replacementString)
{
BtnLogin.Enabled = (!String.IsNullOrWhiteSpace (TextLogin.Text)) && (!String.IsNullOrEmpty (TextPass.Text));
if (BtnLogin.Enabled) {
BtnLogin.SetTitleColor (UIColor.Black, UIControlState.Normal);
} else {
BtnLogin.SetTitleColor (UIColor.Gray, UIControlState.Normal);
}
return true;
}
Can I make this extraction in obj C?
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
AsynchronousRequestCompletionHandler(response, data, error);
}];
EDITED:
You can make convenient things with C++:
template <class T>
void SendAsyncRequest(NSURLRequest* request, NSOperationQueue* opQueue, T func)
{
[NSURLConnection sendAsynchronousRequest:request
queue:opQueue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
func(response, data, error);
}];
}
And then you will be able call this (don't forget rename .m into .mm):
SendAyncRequest(request, [NSOperationQueue mainQueue], AsynchronousRequestCompletionHandler);
Is it what you need?
Note the parameter is a block, not a method. So you can't use a method (or function) instead of it.
typedef void (^ResponseCompletionBlock) (NSURLResponse *, NSData *, NSError *);
ResponseCompletionBlock completionHandler = ^(NSURLResponse *response, NSData *data, NSError *error) {
[self doSomethingWithData:data];
outstandingRequests--;
if (outstandingRequests == 0) {
[self doSomethingElse];
}
};
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:completionHandler];
If you want to handle it in a method, the simplest solution is
ResponseCompletionBlock completionHandler = ^(NSURLResponse *response, NSData *data, NSError *error) {
[self asynchronousRequestCompletionHandlerForResponse:response
withData:data
andError:error];
};
I don't recommend using plain C functions in this context. Don't use C# language patterns/naming rules in Obj-C.

Resources