寫在前面:在接口的設(shè)計上,一般來說都會有兩個 statusCode:一個代表通訊協(xié)議層面,一個代表業(yè)務(wù)層面。但是,在某些特定的場景,這兩者之間可能混淆。也就是說,程序需要在通訊失敗的情況下,獲取服務(wù)器返回給前端的一些報文信息。
-
為什么會存在這樣的問題
1.正常情況下,交易應(yīng)該是:通訊狀態(tài)碼(200)+業(yè)務(wù)邏輯狀態(tài)碼(自定義)去處理每只交易的。但某個例存在,通訊狀態(tài)碼(非200)+業(yè)務(wù)邏輯狀態(tài)碼(自定義)去處理某些特殊情況。
2.在AF3.0版本中 當(dāng)交易處理完畢時,當(dāng)http通訊協(xié)議級別上返回的狀態(tài)碼是200時,框架會將業(yè)務(wù)邏輯報文返回給上一層;在通訊失敗時,返回的是 錯誤信息,并沒有將我們需要的報文返回給上一層。
-
該問題適用的場景
-
AF2.0 ,獲取服務(wù)器返回的錯誤信息
AFHTTPRequestOperation *operation =[[AFHTTPRequestOperation alloc]initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
success(operation, responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//通訊協(xié)議狀態(tài)碼
NSInteger statusCode = operation.response.statusCode;
//服務(wù)器返回的業(yè)務(wù)邏輯報文信息
NSDictionary *dict = operation.responseObject;
failure(operation,error);
}];
為了簡潔上面的代碼刪除了一些公共方法,在af的失敗回調(diào)方法中,operation的這兩個方法,可以將服務(wù)器返回的信息幫你取到。
那么,為什么2.0會如此輕易完成目標(biāo)呢,我們不妨看一下其post方法是如何實現(xiàn)的。
#pragma mark - AFHTTPRequestOperation
-(void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
// completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
self.completionBlock = ^{
if (self.completionGroup) {
dispatch_group_enter(self.completionGroup);
}
//在這個位置 我們已經(jīng)找到了 請求完畢時處理方法
dispatch_async(http_request_operation_processing_queue(), ^{
//當(dāng)通訊失敗的時候,failure返回的是(self,self.error),而這個self指代的當(dāng)前對象是AFHTTPRequestOperation實例,在它的屬性方法中,存在responseObject,因此2.0方法,天然的可以解決此類問題
if (self.error) {
if (failure) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
id responseObject = self.responseObject;
if (self.error) {
if (failure) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
success(self, responseObject);
});
}
}
}
if (self.completionGroup) {
dispatch_group_leave(self.completionGroup);
}
});
};
#pragma clang diagnostic pop
}
從源碼中,我們可以發(fā)現(xiàn):當(dāng)通訊失敗的時候,failure返回的是(self,self.error),而這個self指代的當(dāng)前對象是AFHTTPRequestOperation實例,在它的屬性方法中,存在responseObject,因此2.0版本,天然的可以解決此類問題
-
AF3.0 ,如何獲取服務(wù)器返回的部分信息
[manager POST:urlStr parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
success(manager,responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse*)task.response;
//通訊協(xié)議狀態(tài)碼
NSInteger statusCode = response.statusCode;
//服務(wù)器返回的業(yè)務(wù)邏輯報文信息
NSDictionary *json = __dataSource.loginTimeOutDic;
failure(task,error);
}];
在3.0版本中,通訊協(xié)議的狀態(tài)碼還是很容易獲取到的。但是,業(yè)務(wù)邏輯報文就沒那么容易拿到了。這個問題,我們可以往下研究一下3.0的實現(xiàn)代碼。
1.3.0版本的post請求方法
-(NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))uploadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
//在這里發(fā)情了請求,我們繼續(xù)往下探尋
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];
[dataTask resume];
return dataTask;
}
-(NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
//在這個位置 我們找到了 post請求處理完畢時的回調(diào)方法
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
//先使用一種low逼的方法 用一個全局變量將邏輯報文信息帶回
__dataSource.loginTimeOutDic = responseObject;
//原因在這里,3.0版本并沒有將邏輯報文返回到上一頁面
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
return dataTask;
}
2.這種修改第三方成熟框架的方法,從個人開發(fā)經(jīng)驗來說,并不推崇。
當(dāng)使用cocopods管理第三方工具的時候,并沒有辦法去修改第三方的框架,當(dāng)更新之后,會出現(xiàn)代碼被覆蓋掉的問題。而且,修改這類代碼可能會出現(xiàn)意想不到的問題,雖然可以通過擴展的方式,不影響原代碼邏輯,但畢竟是不方便。因此,我推薦用下面這種方法去實現(xiàn),這個功能。
[manager POST:urlStr parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
success(manager,responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse*)task.response;
//通訊協(xié)議狀態(tài)碼
NSInteger statusCode = response.statusCode;
//服務(wù)器返回的業(yè)務(wù)邏輯報文信息
NSString* errResponse = [[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding];
NSDictionary *json = [self dictionaryWithJson_String:errResponse];
failure(task,error);
}];
注意,在這里取值的userInfo,是根據(jù)post請求完畢處理的方法,下面發(fā)現(xiàn)的代理方法中,