I followed this Screencast... http://nsscreencast.com/episodes/6-afnetworking
My singleton AFHTTPClient code is...
+ (MyClient *)sharedInstance
{
static dispatch_once_t once;
static MyClient *myClient;
dispatch_once(&once, ^ { myClient = [[MyClient alloc] initWithBaseURL:[NSURL URLWithString:MyBaseURL]];});
return myClient;
}
- (id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if (self) {
// these are not actual values but I am setting default headers.
[self setDefaultHeader:#"sdfg" value:#"4"];
[self setDefaultHeader:#"std" value:#"3"];
[self setDefaultHeader:#"reg" value:#"5"];
[self setDefaultHeader:#"yu" value:#"1"];
[self setDefaultHeader:#"xv" value:#"3"];
[self setDefaultHeader:#"hmm" value:#"5"];
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
}
return self;
}
Then I'm executing it like...
[[MyClient sharedInstance] getPath:#"blah.php" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSMutableArray *stats = [NSMutableArray array];
// it crashes on the next line because responseObject is NSData
for (NSDictionary *dictionary in responseObject) {
CCStatistic *stat = [[CCStatistic alloc] initWithDictionary:dictionary];
[stats addObject:stat];
}
self.stats = stats;
[self.tableView reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error retrieving!");
NSLog(#"%#", error);
}];
It all works fine. I've intercepted it with Charles and it is sending the correct request and receiving the correct JSON except the operation is not a JSON operation.
So the responseObject is NSData not the JSON object that I was expecting.
An I missing any config to use the JSON operation?
The line of code that in your case is deciding whether the request is processable by a JSON operation is the following:
return [[self acceptableContentTypes] intersectsSet:AFContentTypesFromHTTPHeader([request valueForHTTPHeaderField:#"Accept"])];
As explained here by Mattt Thompson (the author of AFNetworking) you have to set the Accept header for the request to application/json.
This is not intuitive and it's highly implementation-dependent, but it works.
Add
[self setDefaultHeader:#"Accept" value:#"application/json"];
to your client initialization and it should be ok, regardless of the path extension.
OK, so there is a check in the AFNetworking code that throes out the JSON operation if the request url extension is not json.
Because mine is php it's throwing it out.
Changing the code worked.
My Change
In the AFJSONRequestOperation method...
+ (BOOL)canProcessRequest:(NSURLRequest *)request {
return [[[request URL] pathExtension] isEqualToString:#"json"] || [super canProcessRequest:request];
}
I changed it to...
+ (BOOL)canProcessRequest:(NSURLRequest *)request {
// added php to the request valid path extensions.
return [[[request URL] pathExtension] isEqualToString:#"json"] || [super canProcessRequest:request] || [[[request URL] pathExtension] isEqualToString:#"php"];
}
It's not really a bug per se more like a random syntax style of the requests that are open to me.
Is it normal to have .php requests returning JSON?
Related
i have been trying to implement the follow code , but I am having a hard time understanding the following code:
- (void)getRoutesWithStopName:(NSString *) stopName
success:(void (^)(NSArray *routes))success
error:(void (^)(NSString *errorMsg)) error
{
[[self AFManagerObject] POST:GET_ROUTES
parameters:#{#"params" : #{ #"stopName": [NSString stringWithFormat:#"%%%#%%",[stopName lowercaseString]]} }
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *routesRows = responseObject[#"rows"];
NSMutableArray *routes = [[NSMutableArray alloc] initWithCapacity:routesRows.count];
for(NSDictionary *dicRoute in routesRows)
{
FLBRoute *route = [[FLBRoute alloc] initWithAttrs:dicRoute];
[routes addObject:route];
}
success(routes);
}
failure:^(AFHTTPRequestOperation *operation, NSError *err) {
error(err.description);
}
];
}
I tried learning about blocks but I still can not understand what is going on here. Can you provide me a step by step explanation of the code ?
actually here used for webserviceCall
step-1
- (void)getRoutesWithStopName:(NSString *) stopName
success:(void (^)(NSArray *routes))success
error:(void (^)(NSString *errorMsg)) error
// here pass the one NSString and get the response using NSArray and failure using NSString
step-2
// here used AFNEtworking for call web service
//request block
[self AFManagerObject] -- NSObject class for AFNetworking method place.
POST:GET_ROUTES --> post is default function of request Type, GET_ROUTES --> your Macro class for Request URL
parameters --> send the parameter to server
[[self AFManagerObject] POST:GET_ROUTES
parameters:#{#"params" : #{ #"stopName": [NSString stringWithFormat:#"%%%#%%",[stopName lowercaseString]]} }
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
/*********** success response serlize and store into Array**********/
NSArray *routesRows = responseObject[#"rows"];
NSMutableArray *routes = [[NSMutableArray alloc] initWithCapacity:routesRows.count];
for(NSDictionary *dicRoute in routesRows)
{
FLBRoute *route = [[FLBRoute alloc] initWithAttrs:dicRoute];
[routes addObject:route];
// this is your NSObject class for save the details ,
}
success(routes);
/************** success stop **********/
}
/*********** error if request is fail ************/
failure:^(AFHTTPRequestOperation *operation, NSError *err) {
error(err.description);
}
];
/*********** error if request is stop ************/
I think you need to read a little more about callbacks https://en.m.wikipedia.org/wiki/Callback_(computer_programming) and blocks https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html and https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Blocks/Articles/00_Introduction.html
Basically the method send a POST request and as you know it needs some time for the request to be sent to the server and for the server to respond. You don't want in this time your application to be freezed, so 2 callbacks are used, 1 for success case and 1 for failure case. A block callback is just a block of code that you want to be executed later, when the server will respond back, being a success or failure.
I am quite new to Objective-C & have to dynamically change the value of #property (strong, nonatomic) NSMutableArray *allCategories from inside of AFHTTPRequestOperationManager in success block.
[self.allCategories addObject:tempObject]; doesn't change the value of allCategories while iterating in a loop.
The variable has been initialized as self.allCategories = [[NSMutableArray alloc]init]; in viewDidLoad.
I have also tried creating a temporary variable as __block NSMutableArray *tempCategories = [[NSMutableArray alloc]init]; before initiating AFHTTPRequestOperationManager object. tempCategories doesn't even retain its value.Can't figure out what's happening.EditSorry for inconvenienceviewDidLoad has the following code self.allCategories = [[NSMutableArray alloc]init];[self loadData];Here's the code
-(NSMutableArray *)loadData
{
__block NSMutableArray *tempCategories = [[NSMutableArray alloc]init];
manager = [AFHTTPRequestOperationManager manager];
[manager GET:kAPICategoryList
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
// downcast id to NSMutableDictionary
NSMutableDictionary *json = (NSMutableDictionary *)responseObject;
// check if dictionary is non nil has at least 1 element
if (json != nil && [json count] >= 1) {
// NSLog(#"json:\t%#", json);
// check json is non nil & has success message
if ([json objectForKey:kAPIKeyCategoryRoot] != nil) {
NSArray *arrCategoriesRoot = [json objectForKey:kAPIKeyCategoryRoot];
// check categories has some data
if (arrCategoriesRoot.count >= 1) {
for (int i = 0; i < arrCategoriesRoot.count; i++) {
SomeModel *pCategory;
NSDictionary *dctCategorySingle = [arrCategoriesRoot objectAtIndex:i];
// check category has sub category
if ([dctCategorySingle objectForKey:kAPIKeyCategorySubCategory] != nil) {
// create category with sub category
pCategory = [[SomeModel alloc]initWithSubCategorisedCategoryID:[dctCategorySingle objectForKey:kAPIKeyCategoryID]
name:[dctCategorySingle objectForKey:kAPIKeyCategoryName]
image:kIMGCategoryDefault
subCategory:[dctCategorySingle objectForKey:kAPIKeyCategorySubCategory]];
} else{
// create just a category
pCategory = [[SomeModel alloc]initWithCategoryID:[dctCategorySingle objectForKey:kAPIKeyCategoryID]
name:[dctCategorySingle objectForKey:kAPIKeyCategoryName]
image:kIMGCategoryDefault];
} // else just
[tempCategories addObject:pCategory];
[_allCategories addObject:pCategory];
} // for
NSLog(#"categories count %lu", [self.allCategories count]);
} // if count >= 1
}
else if ([json objectForKey:kAPIRespMsgCategoryFetchErrKey] != nil) {
[Utility showAlertWithTitle:kAPIRespMsgCategoryFetchErrKey
message:[json objectForKey:kAPIRespMsgCategoryFetchErrVal]
button:kMsgButtonOkayTtl];
}
} else {
// error in login => enable login
NSLog(#"%#", kMsgNetworkEmptyJSON);
}
}
// network error
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error %#", [error localizedDescription]);
}];
NSLog(#"tempCategories count %lu", [tempCategories count]);
return tempCategories;
}
Here's the output form NSLog:2015-03-19 18:27:17.845 MyProject[4011:121268] viewDidLoad
2015-03-19 18:27:18.133 MyProject[4011:121268] tempCategories count 0
2015-03-19 18:27:18.136 MyProject[4011:121268] numberOfRowsInSection count 0
2015-03-19 18:27:18.137 MyProject[4011:121268] numberOfRowsInSection count 0
2015-03-19 18:27:19.019 MyProject[4011:121268] categories count 20when loadData finishes allCategories has not data in it (nil).
As far as I know it should work that way.. are you sure your success block is being called before you check the content of allCategories?
A success block work asynchronously, which means it will be executed only when the RequestOperationis completed (which can take a long time if you're downloading something big)
If you are trying to get the value of allCategories before the success block is executed you won't get what you're expecting. I would recommend using breakpoints or NSLog on your success block to see if it's been executed when you think it's doing it.
e.g
...
successBlock:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"Success");
[self.allCategories addObject:tempObject]
}]; //End of request
[operation start]; //Begin executing the AFHTTPOperation
NSLog("%#",self.allCategories.description); //probably nil or empty
//since the success block hasn't been called yet
EDIT:
As I though, you are returning a value before is been set by the async operation, to return a value from an async operation I would suggest take a look to this answer and this one . Also you should read a bit of how async task work.
Basically what you want to do with async operations/tasks is make sure the value will be available when you want to use it. The main issue with that is that you don't know when the value will be set, but you can make sure what you want to do whenever it's set.
To do that you can create a simple method with a custom completion block
- (void)myCustomMethodWithCompletionBlock: (void (^)(NSArray *))completion {
//Do your request
//...
successBlock:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"Success");
completionBlock(allCategories);
}]; //End of request
}
Meanwhile in your main method you call
[self myCustomMethodWithCompletionBlock:^(NSArray *allCategories) {
self.allCategories = allCategories;
//Do other stuff you need to with that variable since now you are
//sure the value will be set unless the operation failed
}];
I had the same problem a few days ago. My problem was my array seems nil, array allocations in viewdidload method may be your request run before viewDidLoad. Check it with debug if you see the array is nill then alloc array different place.
P.S: I m not expert but may be it's the same problem with me.
Try this:
dispatch_async(dispatch_get_main_queue(), ^{
[self.allCategories addObject:tempObject];
});
Define NSMutableArray with following line.
#property (nonatomic, strong) NSMutableArray * arrData;
initializein viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
self.arrData = [NSMutableArray array];
}
call following method with any UIButton action for see output OR working behavior
- (void) TestMethod {
dispatch_queue_t queue = dispatch_queue_create("myQueue", 0);
dispatch_async(queue, ^{
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL urlWithEncoding:#"https://www.google.co.in/?gws_rd=ssl"]];
[httpClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
[httpClient setDefaultHeader:#"Accept" value:#"application/json"];
[httpClient setParameterEncoding:AFJSONParameterEncoding];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:#"" parameters:nil];
[request setTimeoutInterval:180];
[AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObject:#"text/html"]];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
[self.arrData addObject:[NSDictionary dictionaryWithObjectsAndKeys:#"test",#"t3da",#"adsf",#"afds", nil]];
dispatch_semaphore_signal(sema);
} failure:^ (NSURLRequest *request, NSURLResponse *response, NSError *error, id json){
[self.arrData addObject:[NSDictionary dictionaryWithObjectsAndKeys:#"test",#"t3da",#"adsf",#"afds", nil]];
dispatch_semaphore_signal(sema);
}];
[operation start];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
DLog(#"arrData = %#",self.arrData);
});
}
I've been using AFNetworking 2.0 in my app.
I've noticed that if my web-service returns a 500 status code I do not get the body of the response.
Here is an example of my php code
try
{
$conn = new PDO( "sqlsrv:server=$serverName;Database = $database", $uid, $pwd);
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
return $conn;
}
catch( PDOException $e )
{
$response->status(500);
echo( "Connection Error: " . $e->getMessage() );
}
If I use a simple rest client this is an example of a response body.
Connection Error: SQLSTATE[08001]: [Microsoft][SQL Server Native Client 11.0]SQL Server Network Interfaces: Error Locating Server/Instance Specified [xFFFFFFFF].
However this seems to be the only response I can get from AFNetworking
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=0x15e58fa0 {NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}
This is the part of my objective-c code that does this.
...} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#",error.description);
}];
Is there a way I can get the response body?
Edit: More code for clarification
Below is part of my subclass of AFHTTPSessionManager
#implementation MSMAMobileAPIClient
+ (MSMAMobileAPIClient *)sharedClient {
static MSMAMobileAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[MSMAMobileAPIClient alloc] initWithDefaultURL];
});
return _sharedClient;
}
- (id)initWithDefaultURL {
return [self initWithBaseURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://%#/mamobile/index.php/" ,[[NSUserDefaults standardUserDefaults] stringForKey:#"serviceIPAddress"]]]];
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
self.responseSerializer = [AFCompoundResponseSerializer compoundSerializerWithResponseSerializers:#[[AFJSONResponseSerializer serializer], [AFHTTPResponseSerializer serializer]]];
return self;
}
I tried setting the response serializer to a AFCompoundResponseSerializer but it didn't seem to make a difference
Below is an example of a subclass that I call the Librarian.
-(void)searchForItemWithString:(NSString *)searchString withCompletionBlock:(arrayBlock)block {
self.inventorySearchBlock = block;
NSDictionary *parameters = #{#"query": searchString};
[[MSMAMobileAPIClient sharedClient] GET:#"inventory/search" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
if (!responseObject) {
NSLog(#"Error parsing JSON");
} else {
//do stuff with the json dictionary that's returned..
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error: %#",error.description);
}];
}
UPDATE: I have created a github repository to contain the latest code I am using. All changes will be posted there. https://github.com/Hackmodford/HMFJSONResponseSerializerWithData
The answer comes from this issue on github.
https://github.com/AFNetworking/AFNetworking/issues/1397
gfiumara is the dev who came up with this. I have only slightly modified his subclass of AFJSONResponseSerializer to include an actual string instead of the NSData
//MSJSONResponseSerializerWithData.h
#import "AFURLResponseSerialization.h"
/// NSError userInfo key that will contain response data
static NSString * const JSONResponseSerializerWithDataKey = #"JSONResponseSerializerWithDataKey";
#interface MSJSONResponseSerializerWithData : AFJSONResponseSerializer
#end
// MSJSONResponseSerializerWithData.m
#import "MSJSONResponseSerializerWithData.h"
#implementation MSJSONResponseSerializerWithData
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (*error != nil) {
NSMutableDictionary *userInfo = [(*error).userInfo mutableCopy];
userInfo[JSONResponseSerializerWithDataKey] = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSError *newError = [NSError errorWithDomain:(*error).domain code:(*error).code userInfo:userInfo];
(*error) = newError;
}
return (nil);
}
return ([super responseObjectForResponse:response data:data error:error]);
}
#end
Here is an example of how I use it in the failure block.
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#",[error.userInfo objectForKey:#"JSONResponseSerializerWithDataKey"]);
}];
You need to use AFCompoundSerializer to tell the AFNetworking framework how to process all of the possible responses it could receive. By default it will only try to map JSON. A compound serializer will work through the serializers until it finds one that doesn't raise an error.
You want to use:
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers
on AFCompoundResponseSerializer (in AFURLResponseSerialization.h).
You need to pass an array of serializers that can handle the response. One of the serializers in the array should be an instance of AFHTTPResponseSerializer to handle your error responses.
If you include my category in your project, it's as simple as the following:
[mySessionManager POST:#"some-api" parameters:params success:^(NSURLSessionDataTask *task, NSDictionary *responseObject) {
...
} failure:^(NSURLSessionDataTask *task, NSError *error) {
id responseObject = error.userInfo[kErrorResponseObjectKey];
... do something with the response ...
}];
Here's the code for my category. It swizzles AFURLSessionManager to inject a shim into the completion handler. The shim puts the response into the NSError's userInfo.
https://gist.github.com/chrishulbert/35ecbec4b37d36b0d608
i am new to iOS programming, still learning.
EDIT: !!!!!! Everything in my code works. My question is about the delegation pattern i use,
if i am generating problems in the background that i have no idea of, or if there is a better way to handle my situation in AFNetworking...
I have created an API for my app by subclassing AFHTTPSessionManager.
My API creates a singleton and returns it and supplies public functions for various requests. And those functions create parameter lists, and make GET requests on the server like this:
- (void)getCharacterListForKeyID:(NSString *)keyID vCode:(NSString *)vCode sender:(id)delegate
{
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
parameters[#"keyID"] = keyID;
parameters[#"vCode"] = vCode;
[self GET:#"account/Characters.xml.aspx" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
self.xmlWholeData = [NSMutableDictionary dictionary];
self.errorDictionary = [NSMutableDictionary dictionary];
NSXMLParser *XMLParser = (NSXMLParser *)responseObject;
[XMLParser setShouldProcessNamespaces:YES];
XMLParser.delegate = self;
[XMLParser parse];
if ([delegate respondsToSelector:#selector(EVEAPIHTTPClient:didHTTPRequestWithResult:)]) {
[delegate EVEAPIHTTPClient:self didHTTPRequestWithResult:self.xmlWholeData];
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
if ([delegate respondsToSelector:#selector(EVEAPIHTTPClient:didFailWithError:)]) {
[delegate EVEAPIHTTPClient:self didFailWithError:error];
}
}];
}
I was using a normal protocol/delegate method earlier. But once i make calls this API more than once like this: (IT WAS LIKE THIS:)
EVEAPIHTTPClient *client = [EVEAPIHTTPClient sharedEVEAPIHTTPClient];
client.delegate = self;
[client getCharacterListForKeyID:self.keyID vCode:self.vCode];
Previous call's delegate was being overwritten by next. So i changed to above style. Passing sender as an argument in the function:
EVEAPIHTTPClient *client = [EVEAPIHTTPClient sharedEVEAPIHTTPClient];
[client getCharacterListForKeyID:self.keyID vCode:self.vCode sender:self];
And i pass this sender to GET request's success and failure blocks.
What i wonder is : "Is this a good programming practice ?". Passing objects to blocks like this should be avoided if possible ? Is there any other more elegant way in AFHTTPSessionManager to handle this type of work (making same GET request over and over with different parameters and returning results to the respective request owners) more elegantly ?
Delegation pattern falters when it comes to simplicity and asynchronous request processing. You should be using blocks, here's an example
Your server class:
static NSString *const kNews = #"user_news/"; // somewhere above the #implementation
- (NSURLSessionDataTask *)newsWithPage:(NSNumber *)page
lastNewsID:(NSNumber *)lastNewsID
completion:(void (^)(NSString *errMsg, NSArray *news, NSNumber *nextPage))completionBlock {
return [self GET:kNews
parameters:#{#"page" : page,
#"news_id" : lastNewsID
}
success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray *news = nil;
NSNumber *nextPage = nil;
NSString *errors = [self errors:responseObject[#"errors"]]; // process errors
if ([responseObject[#"status"] boolValue]) {
news = responseObject[#"news"];
nextPage = responseObject[#"next_page"];
[self assignToken];
}
completionBlock(errors, news, nextPage);
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSString *errors = [self errors:error];
completionBlock(errors, nil, nil);
}];
}
The caller
- (void)dealloc {
[_task cancel]; // you don't want this task to execute if user suddenly removes your controller from the navigation controller's stack
}
- (void)requestNews {
typeof(self) __weak wself = self; // to avoid the retain cycle
self.task = [[GSGServer sharedInstance] newsWithPage:self.page
lastNewsID:self.lastNewsID
completion:^(NSString *errMsg, NSArray *news, NSNumber *nextPage) {
if (errMsg) {
[GSGAppDelegate alertQuick:errMsg]; // shortcut for posting UIAlertView, uses errMsg for message and "Error" as a title
return;
}
[wself.news addObjectsFromArray:news];
wself.lastNewsID = [wself.news firstObject][#"id"];
wself.page = nextPage;
[wself.tableView reloadData];
}];
}
I've been using AFNetworking 2.0 in my app.
I've noticed that if my web-service returns a 500 status code I do not get the body of the response.
Here is an example of my php code
try
{
$conn = new PDO( "sqlsrv:server=$serverName;Database = $database", $uid, $pwd);
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
return $conn;
}
catch( PDOException $e )
{
$response->status(500);
echo( "Connection Error: " . $e->getMessage() );
}
If I use a simple rest client this is an example of a response body.
Connection Error: SQLSTATE[08001]: [Microsoft][SQL Server Native Client 11.0]SQL Server Network Interfaces: Error Locating Server/Instance Specified [xFFFFFFFF].
However this seems to be the only response I can get from AFNetworking
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=0x15e58fa0 {NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}
This is the part of my objective-c code that does this.
...} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#",error.description);
}];
Is there a way I can get the response body?
Edit: More code for clarification
Below is part of my subclass of AFHTTPSessionManager
#implementation MSMAMobileAPIClient
+ (MSMAMobileAPIClient *)sharedClient {
static MSMAMobileAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[MSMAMobileAPIClient alloc] initWithDefaultURL];
});
return _sharedClient;
}
- (id)initWithDefaultURL {
return [self initWithBaseURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://%#/mamobile/index.php/" ,[[NSUserDefaults standardUserDefaults] stringForKey:#"serviceIPAddress"]]]];
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
self.responseSerializer = [AFCompoundResponseSerializer compoundSerializerWithResponseSerializers:#[[AFJSONResponseSerializer serializer], [AFHTTPResponseSerializer serializer]]];
return self;
}
I tried setting the response serializer to a AFCompoundResponseSerializer but it didn't seem to make a difference
Below is an example of a subclass that I call the Librarian.
-(void)searchForItemWithString:(NSString *)searchString withCompletionBlock:(arrayBlock)block {
self.inventorySearchBlock = block;
NSDictionary *parameters = #{#"query": searchString};
[[MSMAMobileAPIClient sharedClient] GET:#"inventory/search" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
if (!responseObject) {
NSLog(#"Error parsing JSON");
} else {
//do stuff with the json dictionary that's returned..
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error: %#",error.description);
}];
}
UPDATE: I have created a github repository to contain the latest code I am using. All changes will be posted there. https://github.com/Hackmodford/HMFJSONResponseSerializerWithData
The answer comes from this issue on github.
https://github.com/AFNetworking/AFNetworking/issues/1397
gfiumara is the dev who came up with this. I have only slightly modified his subclass of AFJSONResponseSerializer to include an actual string instead of the NSData
//MSJSONResponseSerializerWithData.h
#import "AFURLResponseSerialization.h"
/// NSError userInfo key that will contain response data
static NSString * const JSONResponseSerializerWithDataKey = #"JSONResponseSerializerWithDataKey";
#interface MSJSONResponseSerializerWithData : AFJSONResponseSerializer
#end
// MSJSONResponseSerializerWithData.m
#import "MSJSONResponseSerializerWithData.h"
#implementation MSJSONResponseSerializerWithData
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (*error != nil) {
NSMutableDictionary *userInfo = [(*error).userInfo mutableCopy];
userInfo[JSONResponseSerializerWithDataKey] = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSError *newError = [NSError errorWithDomain:(*error).domain code:(*error).code userInfo:userInfo];
(*error) = newError;
}
return (nil);
}
return ([super responseObjectForResponse:response data:data error:error]);
}
#end
Here is an example of how I use it in the failure block.
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#",[error.userInfo objectForKey:#"JSONResponseSerializerWithDataKey"]);
}];
You need to use AFCompoundSerializer to tell the AFNetworking framework how to process all of the possible responses it could receive. By default it will only try to map JSON. A compound serializer will work through the serializers until it finds one that doesn't raise an error.
You want to use:
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers
on AFCompoundResponseSerializer (in AFURLResponseSerialization.h).
You need to pass an array of serializers that can handle the response. One of the serializers in the array should be an instance of AFHTTPResponseSerializer to handle your error responses.
If you include my category in your project, it's as simple as the following:
[mySessionManager POST:#"some-api" parameters:params success:^(NSURLSessionDataTask *task, NSDictionary *responseObject) {
...
} failure:^(NSURLSessionDataTask *task, NSError *error) {
id responseObject = error.userInfo[kErrorResponseObjectKey];
... do something with the response ...
}];
Here's the code for my category. It swizzles AFURLSessionManager to inject a shim into the completion handler. The shim puts the response into the NSError's userInfo.
https://gist.github.com/chrishulbert/35ecbec4b37d36b0d608