问题
- 有用户反映“网络不给力,请稍后再试”,不过当时网络明明是好的
- 概率发生,用着用着就会出现
- 过一会儿再试一下,基本上就好了
调用结构
打开工程,全局搜索“网络不给力”,找到了如下结果
#define NetWork_ERROR @"网络不给力,请稍后再试!"
报错信息统一用了一个宏定义,没有直接写死全局搜索
NetWork_ERROR
,得到的结果如下
+(void)networkError
{
[NSObject cancelPreviousPerformRequestsWithTarget:[SVProgressHUD sharedView] selector:@selector(show) object:nil];
//部分请求发起前用SVProgressHUD遮罩,若此处改为其他控件需加入-SVProgressHUD dismiss方法
[SVProgressHUD showNoIconWithStatus:NetWork_ERROR duration:2];
}
调用第三方库SVProgressHUD
来实现一个类似Android toast
的效果
- 继续往下查找调用情况,大多数结果都出现在下列函数调用中
/**
* 将网络错误和系统错误区分
*
* @param path 请求地址
* @param formDataDic 请求参数
* @param successBlock 处理请求成功结果
* @param serverErrorBlock 处理服务器错误
* @param netErrorBlock 处理网络错误
*/
- (void)performRequestWithPath:(NSString *)path
formDataDic:(NSDictionary *)formDataDic
success:(void (^)(NSDictionary *responseObject))successBlock
serverError:(void(^)(NSDictionary *responseObject))serverErrorBlock
netError:(void(^)(NSError *error))netErrorBlock;
出现在netErrorBlock
中,从函数调用来看,确实是网络错误时的调用。这个函数的具体实现是通过第三方库AFNetworking
来做的
- (void)performRequestWithPath:(NSString *)path formDataDic:(NSDictionary *)formDataDic success:(void (^)(NSDictionary *responseObject))successBlock serverError:(void(^)(NSDictionary *responseObject))serverErrorBlock netError:(void(^)(NSError *error))netErrorBlock {
NSMutableDictionary *postParameters = [[NSMutableDictionary alloc] init];
if (formDataDic != nil) {
[postParameters setDictionary:formDataDic];
}
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.securityPolicy.allowInvalidCertificates = NO;
manager.requestSerializer.timeoutInterval = 10;
[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Accept"];
[manager POST:path parameters:postParameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLOG_DEBUG(@"url = %@ \nresponseObject = %@", path, responseObject);
NSString *resultCode = [responseObject objectForKey:@"resultCode"];
if ([resultCode isEqualToString:@"success"]) {
if (successBlock){
successBlock((NSDictionary *)responseObject);
}
} else if (serverErrorBlock) {
serverErrorBlock((NSDictionary *)responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error){
NSLOG_DEBUG(@"url = %@ \nresponseObject = %@", path, [error localizedDescription]);
if (netErrorBlock) {
netErrorBlock(error);
}
}];
}
AFNetworking
这个第三方库直接代码集成的,版本比较老,不过目前工作正常。
返回结果是一个字典(JSON解析AFNetworking
帮忙做了),key=resultCode
,对应的value存的是服务器自定义的接口处理结果,如果成功,value= success
,否则就是业务失败的情况。不过,这个时候,网络本身是通的。
网络失败,直接用了AFNetworking
的failure
回调的block
,记了一下debug
版本的log
,接口直通。
这样看来,是直接使用了AFNetworking
的网络代码,没有过多的自定义。所以从客户端来看“网络不给力”,大概率是真的服务数据接口不通。因为AFNetworking
这个第三方库几乎已经是iOS
开发中的事实标准了。
对于AFNetworking
的调用结构
- (AFHTTPRequestOperation *)POST:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:nil];
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
这个是数据发送的接口,用了POST
方法
+ (instancetype)manager {
return [[self alloc] initWithBaseURL:nil];
}
- (instancetype)initWithBaseURL:(NSURL *)url {
self = [super init];
if (!self) {
return nil;
}
// Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
self.baseURL = url;
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
self.operationQueue = [[NSOperationQueue alloc] init];
self.shouldUseCredentialStorage = YES;
return self;
}
AFHTTPRequestOperationManager
类中的初始化方法:
- (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);
}
dispatch_async(http_request_operation_processing_queue(), ^{
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
}
AFHTTPRequestOperation
继承自NSOperation
,实现多线程的管理。网络传输完成,是通过NSOperation
的completionBlock
来处理。这里用了GCD
中的dispatch_group
相关的API来完成对success
和failure
两个block
的同步。
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;
self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
self.acceptableContentTypes = nil;
return self;
}
AFHTTPResponseSerializer
类中设定了成功的状态码范围,从200
开始,长度是100
,也就是[200,300)
,前闭后开区间。
使用了比较老的NSURLConnection
网络API
,这个版本的AFNetworking
的版本应该是2.x
iOS网络1——NSURLConnection使用详解
引入日志系统
虽然说用的AFNetworking
版本比较老,并且还是直接源代码集成的,连CocoaPod
也没有用,但是从使用的角度来看还是正确的。出现“网络不给力,请稍后再试”,说明服务器返回了不在[200,300)
中的状态码。
目前的代码中只有一句NSLog
,将出错信息输出,在XCode
中可以看到,当然,借助iTools
这样的工具,也可以在手机上看。
NSLOG_DEBUG(@"url = %@ \nresponseObject = %@", path, [error localizedDescription]);
不过对于目前这种偶现的问题,这种类似内存缓存的日志作用很小。因此,考虑引入专门的日志系统,对这类问题进行日志手机,定期传到后台分析。
以前朋友用过CocoaLumberjack
,说这个不错,这次考虑引入。
从网上介绍的文章来说,使用比较简单。往后台传日志,是将日志读出来,以字符串的形式传给后台。这部分要自己实现一下,传给苹果后台是默认支持的。
CocoaLumberjack
利用 CocoaLumberjack 搭建自己的 Log 系统
CocoaLumberjack使用
iOS第三方库-CocoaLumberjack-DDLog
CocoaLumberjack的ios应用开发使用指南
使用CocoaLumberjack和XcodeColors实现分级Log和控制台颜色