What happens if AFHTTPSessionManager's responseSerializer fails to parse? - objective-c

If AFHTTPSessionManager's responseSerializer fails to parse the response, eg the response isn't a valid JSON payload, what happens with the following code? Does:
A: The success block get called with responseObject=nil, or:
B: The failure block get called?
[[ServiceManager sharedManager].sessionManager GET:#"blah" parameters:params success:^(NSURLSessionDataTask *task, NSDictionary *responseObject) {
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];

Per the documentation, it's B; the failure block is executed when an error is encountered while parsing the response data.
I just checked and for me, the localizedDescription on the error was "The data couldn’t be read because it isn’t in the correct format."

Related

calculateDirectionsWithCompletionHandler not returning with error or response

I'm calling the calculateDirectionsWithCompletionHandler method from MKDirections, but the completion handler is never called. Not even with an error. And since the request is made in the main thread, the entire application gets stuck. I ensure that the directionRequest has source and destination and both locations are not nil. Someone have experienced the same?
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(#"%#", error);
}else{
// Deal with the response
}
}];

iOS special characters in url

I try to make an api call:
api.app.com/foo/search/Öhm
where Öhm is the search term. The problem is that this url causes a bad url exception, while normal chars work well. I tried with
[searchText stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
However this produces a lot of % signs. The api expects Öhm. Any solution for this problem? I am using restkit.
The full error:
Error Domain=NSURLErrorDomain Code=-1000 "bad URL" UserInfo=0x16e719d0
{NSUnderlyingError=0x16d70d10 "bad URL", NSLocalizedDescription=bad
URL} 2014-04-03 18:26:55.404 My App[6844:3807] E
restkit.network:RKObjectRequestOperation.m:243 GET '(null)' (0 / 0
objects) [request=0.0084s mapping=0.0000s total=0.0769s]: Error
Domain=NSURLErrorDomain Code=-1000 "bad URL" UserInfo=0x16e719d0
{NSUnderlyingError=0x16d70d10 "bad URL", NSLocalizedDescription=bad
URL}
The termin is (null) instead of Öhm.
Code:
[[ApiSearchManager sharedManager] search:^(NSArray *result){
//handle success
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
//handle fail - it fails for Öhm
} query:searchTerm];
and on the lower level:
- (void) search:(void (^)(NSArray *))success failure:(void (^)(RKObjectRequestOperation *, NSError *))failure query:(NSString *)query{
[self getObjectsAtPath:[NSString stringWithFormat:#"foo/search/%#", query] parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
if (success) {
success(mappingResult.array);
}
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
if (failure) {
failure(operation, error);
}
}];
}
The % signs are escaping, and that is how the URL should be transmitted. At the server side, the URL gets interpreted (unescaped) and the server-side code will see the special characters as you intended. For example it's very common to see URL's with %20 in them. ASCII character 20 (hex) is a space, so server-side code that obtains the URL (appropriately decoded) see a space in there.
This section of the log:
GET '(null)' (0 / 0 objects) [request=0.0084s mapping=0.0000s total=0.0769s]:
Means you're doing a GET, that the request URL is nil (which would be bad), so status code was obtained because no request was actually sent, it took a little time, blah blah
So, The URL loading system couldn't create a valid URL with which to make your request.
I'm going to guess that the only way you can achieve this is to not have a properly configured baseURL in your RKObjectManager. Check that. (though I would expect that to fail earlier - so you may be reduced to debugging around RKObjectRequestOperation.m, line 243, and back up the call stack)

EXC_BAD_ACCESS when doing a POST using AFNetworking 2.0

My code:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager] ;
manager.requestSerializer = [AFJSONRequestSerializer serializerWithWritingOptions:NSJSONReadingAllowFragments];
manager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
[manager POST:[_urlBase stringByAppendingPathComponent:_urlRequest]
parameters:paramDictionary
success:^(NSURLSessionDataTask *task, id responseObject){
dispatch_async(dispatch_get_main_queue(),^{
[self AFRequestFinished:responseObject];
});
}
failure:^(NSURLSessionDataTask *task, NSError *error){
NSLog(#"JSON ERROR PARAMETERS: %#", error);
}
];
I am using this POST request to send several types of data up to a server along with pictures. I am using something very similar for the GET request and it works fine. Whenever I run this code I get a EXC_BAD_ACCESS CODE=1 error on the following line of AFNetworking 2.0. The responseObject is 0x0:
responseObject = [self.responseSerializer responseObjectForResponse:task.response data:[NSData dataWithData:self.mutableData] error:&serializationError];
The above line of code is within the if/else method in:
- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
UPDATE
I ran instruments on the code, and it there is a zombie present. AFNetworking is trying to make a call to the NSError, but it has been deallocated. I believe this has arisen because the POST call initially succeeds, but there is still an error that is flagged. So it initially thinks there is no error and sets it to nil, but then tries to call for it in the error block of the POST.
If you're using the most recent version, you may be experiencing this known issue when the JSON serializer returns an error. You can work around this until a new release is made by:
removing the #autoreleasepool in the serializer, or
changing the scope of the error to outside the autorelease pool
(Both solutions are outlined in the issue linked above.)
On a side note, there's no need to dispatch to the main queue in the completion handler. AFNetworking guarantees that completion blocks are called on the main thread.

AFNetwork 2.0 POST results in a 403 error

I'm using AFNetworking 2 to perform a simple post operation:
[self.manager POST:#"person"
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (success)
success(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (failure)
failure(error);
}];
Every time this runs, XCode's console says that I got "Request failed: forbidden (403)" as the response. If I run against the exact same url shown in the NSErrorFailingURLKey via curl, I immediately get back the results I'd expect from the POST operation.
I haven't enabled any type of authentication on the script being called. It's just a Restler class. Am I missing a step here?
Adding the 'constructingBodyWithBlock' changed the type of message being sent. Had to remove that in order to make it work.

ERROR happened while deserializing the JSON data

-(void) conn:(NSString *)method{
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
__block NSDictionary *resultBlock = nil;
dispatch_sync(concurrentQueue, ^{
/* Download the json here */
//Create webservice address
NSString *webService = [_baseURL stringByAppendingString:_webService];
//NSLog(#"%#", webService);
//Create error object
NSError *downloadError = nil;
//Create the request
NSMutableURLRequest *req = [self initRequest:webService method:method];
if(req != nil){
//Request the json data from the server
NSData *jsonData = [NSURLConnection
sendSynchronousRequest:req
returningResponse:nil
error:&downloadError];
if(downloadError!=nil){
NSLog(#"DOWNLOAD ERROR %#", downloadError);
}
NSError *error = nil;
id jsonObject = nil;
if(jsonData !=nil){
/* Now try to deserialize the JSON object into a dictionary */
jsonObject = [NSJSONSerialization
JSONObjectWithData:jsonData
options:kNilOptions
error: &error];
}
//Handel the deserialized object data
if (jsonObject != nil && error == nil){
NSLog(#"Successfully deserialized...");
if ([jsonObject isKindOfClass:[NSDictionary class]]){
resultBlock = (NSDictionary *)jsonObject;
//NSLog(#"Deserialized JSON Dictionary = %#", resultBlock);
}
else if ([jsonObject isKindOfClass:[NSArray class]]){
NSArray *deserializedArray = (NSArray *)jsonObject;
NSLog(#"Deserialized JSON Array = %#", deserializedArray);
} else {
/* Some other object was returned. We don't know how to deal
with this situation, as the deserializer returns only dictionaries
or arrays */
}
}
else if (error != nil){
NSLog(#"An error happened while deserializing the JSON data. %#", error);
}else{
NSLog(#"No data could get downloaded from the URL.");
//[self conn:method];
}
}
});
dispatch_sync(dispatch_get_main_queue(), ^{
/* Check if the resultBlock is not nil*/
if(resultBlock != nil){
/*Set the value of result. This will notify the observer*/
[self setResult:resultBlock];
}
});
});
}
Why do I get the following error?
An error happened while deserializing the JSON data. Error
Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be
completed. (Cocoa error 3840.)" (JSON text did not start with array or
object and option to allow fragments not set.) UserInfo=0x20839f80
{NSDebugDescription=JSON text did not start with array or object and
option to allow fragments not set.}
When I change it to
/* Now try to deserialize the JSON object into a dictionary */
jsonObject = [NSJSONSerialization
JSONObjectWithData:jsonData
options:NSJSONReadingAllowFragments
error: &error];
}
I get the following error:
An error happened while deserializing the JSON data. Error
Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be
completed. (Cocoa error 3840.)" (Invalid value around character 0.)
UserInfo=0x20888760 {NSDebugDescription=Invalid value around character
0.}
I changed my connection from LTE to wifi and now I get
504 error and NSLog(#"No data could get downloaded from the URL.");
You should fix these issues in your code first:
Properly check for errors in methods which provide a pointer to a reference to an NSError object as the last parameter, e.g.: - (BOOL) doSomething:(NSError**)error, or -(NSData*) doSomething:(NSError**)error
In order test for an error correctly, you have to check the return value of the method only. Those methods indicate an error condition with a "special return value". For example, they return NO or nil - as always specified in the documentation. Only after the method indicated an error, the provided error parameter contains a meaningful value - that is, it points to an NSError object created by the method. Note that this parameter may also become none NULL when the method succeeded, in which case that has no "meaning".
Web services usually can provide several formats of the requested resource. If you don't specify which format you want the server to encode the resource, you get a default format - which is not necessarily JSON.
In order to be explicit about the desired format of the resource, set a corresponding "Accept" header. For example, if you wish the format in JSON you would set a header: "Accept: application/json" in your request.
Web services may have reasons not to respond with the resource you requested. In order to be sure you got the response that you requested, you need to check the response for status code and MIME type in order to ensure you actually received a JSON response.
It seems, you are a bit uncertain about how to use dispatch functions to your advantage. If you use the synchronous convenient method sendSynchronousRequest:... You certainly need to wrap it in only one dispatch_async function. If you then want to set the result on the main thread, you certainly want to use dispatch_async, not dispatch_sync.
However, it would be an improvement if you would use sendAsynchronousRequest:... instead. And only if you would use NSURLConnection in asynchronous mode and implement the NSURLConnection delegate methods - which I strongly recommend - it would actually become great ;)
So, I think, once you fixed your code, you may be able to answer the original question yourself, or get better error responses from the server, or the error magically disappeared ;)