可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
When switched to AFNetworking 2.0 the AFHTTPClient has been replaced by AFHTTPRequestOperationManager / AFHTTPSessionManager (as mentioned in the migration guide). The very first issue I came across when using the AFHTTPSessionManager is how to retrieve the body of the response in the failure block?
Here's an example:
[self.sessionManager POST:[endpoint absoluteString] parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
// How to get the status code?
} failure:^(NSURLSessionDataTask *task, NSError *error) {
// How to get the status code? response?
}];
In the success block I would like to retrieve response's status code.
In the failure block I would like to retrieve both response's status code and the content (which is JSON in this case that describes the server-side error).
The NSURLSessionDataTask has a response property of type NSURLResponse, which has not statusCode field. Currently I'm able to retrieve statusCode like this:
[self.sessionManager POST:[endpoint absoluteString] parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
// How to get the status code?
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
DDLogError(@"Response statusCode: %i", response.statusCode);
}];
But this looks ugly to me. And still can't figure about the response's body.
Any suggestions?
回答1:
You can access the “data” object directly from AFNetworking by using the “AFNetworkingOperationFailingURLResponseDataErrorKey” key so there is no need for subclassing the AFJSONResponseSerializer. You can the serialize the data into a readable dictionary.
Here is some sample code to get JSON Data :
NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil];
Here is the code to Get Status code in the Failure block:
NSHTTPURLResponse* r = (NSHTTPURLResponse*)task.response;
NSLog( @"success: %d", r.statusCode );
回答2:
After of read and research for several days, It worked for me:
1) You have to build your own subclass of AFJSONResponseSerializer
File : JSONResponseSerializerWithData.h:
#import "AFURLResponseSerialization.h"
/// NSError userInfo key that will contain response data
static NSString * const JSONResponseSerializerWithDataKey = @"JSONResponseSerializerWithDataKey";
@interface JSONResponseSerializerWithData : AFJSONResponseSerializer
@end
File: JSONResponseSerializerWithData.m
#import "JSONResponseSerializerWithData.h"
@implementation JSONResponseSerializerWithData
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
id JSONObject = [super responseObjectForResponse:response data:data error:error];
if (*error != nil) {
NSMutableDictionary *userInfo = [(*error).userInfo mutableCopy];
if (data == nil) {
// // NOTE: You might want to convert data to a string here too, up to you.
// userInfo[JSONResponseSerializerWithDataKey] = @"";
userInfo[JSONResponseSerializerWithDataKey] = [NSData data];
} else {
// // NOTE: You might want to convert data to a string here too, up to you.
// userInfo[JSONResponseSerializerWithDataKey] = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
userInfo[JSONResponseSerializerWithDataKey] = data;
}
NSError *newError = [NSError errorWithDomain:(*error).domain code:(*error).code userInfo:userInfo];
(*error) = newError;
}
return (JSONObject);
}
2) Setup your own JSONResponseSerializer in your AFHTTPSessionManager
+ (instancetype)sharedManager
{
static CustomSharedManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[CustomSharedManager alloc] initWithBaseURL:<# your base URL #>];
// *** Use our custom response serializer ***
manager.responseSerializer = [JSONResponseSerializerWithData serializer];
});
return (manager);
}
Source: http://blog.gregfiumara.com/archives/239
回答3:
You can get the status code like this, read the failure block...
NSURLSessionDataTask *op = [[IAClient sharedClient] POST:path parameters:paramsDict constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
} success:^(NSURLSessionDataTask *task, id responseObject) {
DLog(@"\n============= Entity Saved Success ===\n%@",responseObject);
completionBlock(responseObject, nil);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
DLog(@"\n============== ERROR ====\n%@",error.userInfo);
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
int statuscode = response.statusCode;}
回答4:
There is another approach besides the accepted answer.
AFNetworking is calling your failure block, sans any response object, because it believes a true failure has occurred (e.g. a HTTP 404 response, perhaps). The reason it interprets 404 as an error is because 404 isn't in the set of "acceptable status codes" owned by the response serializer (the default range of acceptable codes is 200-299). If you add 404 (or 400, or 500, or whatever) to that set then a response with that code will be deemed acceptable and will be routed to your success block instead - complete with the decoded response object.
But 404 is an error! I want my failure block to be called for errors! If that's the case then use the solution referred to by the accepted answer: https://github.com/AFNetworking/AFNetworking/issues/1397. But consider that perhaps a 404 is really a success if you're going to be extracting and processing the content. In this case your failure block handles real failures - e.g. unresolvable domains, network timeouts, etc. You can easily retrieve the status code in your success block and process accordingly.
Now I understand - it might be super nice if AFNetworking passed any responseObject to the failure block. But it doesn't.
_sm = [[AFHTTPSessionManager alloc] initWithBaseURL: [NSURL URLWithString: @"http://www.stackoverflow.com" ]];
_sm.responseSerializer = [AFHTTPResponseSerializer new];
_sm.responseSerializer.acceptableContentTypes = nil;
NSMutableIndexSet* codes = [NSMutableIndexSet indexSetWithIndexesInRange: NSMakeRange(200, 100)];
[codes addIndex: 404];
_sm.responseSerializer.acceptableStatusCodes = codes;
[_sm GET: @"doesnt_exist"
parameters: nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSHTTPURLResponse* r = (NSHTTPURLResponse*)task.response;
NSLog( @"success: %d", r.statusCode );
NSString* s = [[NSString alloc] initWithData: responseObject encoding:NSUTF8StringEncoding];
NSLog( @"%@", s );
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog( @"fail: %@", error );
}];
回答5:
You can access the “data” object directly from AFNetworking by using the “AFNetworkingOperationFailingURLResponseDataErrorKey” key so there is no need for subclassing the AFJSONResponseSerializer. You can the serialize the data into a readable dictionary.
Here is some sample code :
NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil];
回答6:
In Swift 2.0 (in case you cannot use Alamofire yet):
Get status code:
if let response = error.userInfo[AFNetworkingOperationFailingURLResponseErrorKey] as? NSHTTPURLResponse {
print(response.statusCode)
}
Get response data:
if let data = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] as? NSData {
print("\(data.length)")
}
Some JSON REST APIs return error messages in their error responses (Amazon AWS services for example). I use this function to extract the error message from an NSError that has been thrown by AFNetworking:
// Example: Returns string "error123" for JSON { message: "error123" }
func responseMessageFromError(error: NSError) -> String? {
do {
guard let data = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] as? NSData else {
return nil
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [String: String] else {
return nil
}
if let message = json["message"] {
return message
}
return nil
} catch {
return nil
}
}
回答7:
You can get the userInfo
dictionary associated with the NSError
object and traverse down that to get the exact response you need. For example in my case I get an error from a server and I can see the userInfo
like in ScreeShot