问题分析
一个页面,可以通过点击不同的模块获取相应的数据。但是,当用户频繁点击的时候,有的模块网络请求数据返回会比较慢,这个时候返回的数据就会覆盖当前模块的数据。
解决方法
加锁处理
切换模块时,会对同一个API进行多次请求,但因为调用的接口都是一样的,所以最好就是加上锁,防止重复请求造成网络资源浪费。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@synchronized (self) {//加锁,避免数组重复创建添加等问题
static NSMutableArray * successBlocks;//用数组保存回调
static NSMutableArray * failureBlocks;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{//仅创建一次数组
successBlocks = [NSMutableArray new];
failureBlocks = [NSMutableArray new];
});
if (success) {//每调用一次此函数,就把回调加进数组中
[successBlocks addObject:success];
}
if (failure) {
[failureBlocks addObject:failure];
}
static BOOL isProcessing = NO;
if (isProcessing == YES) {//如果已经在请求了,就不再发出新的请求
return;
}
isProcessing = YES;
[self callerPostTransactionId:transactionId parameters:dic showActivityIndicator:showActivityIndicator showErrorAlterView:showErrorAlterView success:^(id responseObject) {
@synchronized (self) {//网络请求的回调也要加锁,这里是另一个线程了
for (successBlock eachSuccess in successBlocks) {//遍历回调数组,把结果发给每个调用者
eachSuccess(responseObject);
}
[successBlocks removeAllObjects];
[failureBlocks removeAllObjects];
isProcessing = NO;
}
} failure:^(id data) {
@synchronized (self) {
for (failureBlock eachFailure in failureBlocks) {
eachFailure(data);
}
[successBlocks removeAllObjects];
[failureBlocks removeAllObjects];
isProcessing = NO;
}
}];
}
取消请求
取消全部请求:
1
[manager.operationQueue cancelAllOperations];
取消单个请求,以post请求为例:
1
2
3
4
5
6
7
AFHTTPRequestOperation *post =[manager POST:nil parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
//doing something
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// error handling.
}];
//Cancel operation
[post cancel];
AFNetworking取消正在进行的网络请求:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//单例模式
+ (HttpManager *)sharedManager
{
static dispatch_once_t once;
dispatch_once(&once, ^{
httpManager = [[HttpManager alloc] init];
});
return httpManager;
}
//网络类初始化
- (id)init{
self = [super init];
if(self)
{
manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
}
return self;
}
1
2
3
4
5
6
7
8
9
[[HttpManager sharedManager] dataFromWithBaseURL:BaseURL path:url method:@"POST" timeInterval:10 params:parmas success:^(NSURLRequest *request, NSURLResponse *response, id JSON) {
} failure:^(NSURLRequest *request, NSURLResponse *response, NSError *error, id JSON) {
} error:^(id JSON) {
} finish:^(id JSON) {
}];
取消正在进行的网络请求
1
2
3
4
5
6
7
8
9
- (void)cancelRequest
{
if ([manager.tasks count] > 0) {
NSLog(@"返回时取消网络请求");
[manager.tasks makeObjectsPerformSelector:@selector(cancel)];
//NSLog(@"tasks = %@",manager.tasks);
}
}
同一个请求多次请求时,短时间忽略相同的请求
当进行刷新操作时,如果在请求还没有返回之前,一直在刷新操作,不管是狂点还是乱点。那么第一个请求发出后,短时间内可以不进行重复请求。
同一个请求多次请求时,取消之前发出的请求
如果是在搜索操作,那么每次输入关键字的时候,之前发出的请求可以取消,仅仅显示最后的请求结果。 采用的方法为创建一个BaseViewModel,所有的请求操作继承BaseViewModel,在发起请求之前进行一次判断。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma mark - 忽略请求
/** 忽略请求,当请求的url和参数都是一样的时候,在短时间内不发起再次请求, 默认3秒 */
- (BOOL)ignoreRequestWithUrl:(NSString *)url params:(NSDictionary *)params;
/** 忽略请求,当请求的url和参数都是一样的时候,在短时间内不发起再次请求 */
- (BOOL)ignoreRequestWithUrl:(NSString *)url params:(NSDictionary *)params timeInterval:(NSTimeInterval)timeInterval;
#pragma mark - 取消之前的请求
/** 取消之前的同一个url的网络请求
* 在failure分支中,判断如果是取消操作,那么不做任何处理
* 在success和failure分支中,都要调用clearTaskSessionWithUrl:方法,进行内存释放
*/
- (void)cancelLastTaskSessionWithUrl:(NSString *)url currentTaskSession:(NSURLSessionTask *)task;
/** 清除url绑定的sessionTask */
- (void)clearTaskSessionWithUrl:(NSString *)url;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@property (nonatomic, strong) NSMutableDictionary *requestTimeMDic;
@property (nonatomic, strong) NSMutableDictionary *cancelTaskMDic;
- (BOOL)ignoreRequestWithUrl:(NSString *)url params:(NSDictionary *)params
{
return [self ignoreRequestWithUrl:url params:params timeInterval:kRequestTimeInterval];
}
- (BOOL)ignoreRequestWithUrl:(NSString *)url params:(NSDictionary *)params timeInterval:(NSTimeInterval)timeInterval
{
NSString *requestStr = [NSString stringWithFormat:@"%@%@", url, [params uq_URLQueryString]];
NSString *requestMD5 = [NSString md5:requestStr];
NSTimeInterval nowTime = [[NSDate date] timeIntervalSince1970];
NSNumber *lastTimeNum = [self.requestTimeMDic objectForKey:requestMD5];
WS(weakSelf);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 超过忽略时间后,将值清空
[weakSelf.requestTimeMDic removeObjectForKey:requestMD5];
});
if (timeInterval < (nowTime - [lastTimeNum doubleValue])) {
if (0.01 > [lastTimeNum doubleValue]) {
[self.requestTimeMDic setObject:@(nowTime) forKey:requestMD5];
}
return NO;
} else {
return YES;
}
}
- (void)cancelLastTaskSessionWithUrl:(NSString *)url currentTaskSession:(NSURLSessionTask *)task
{
NSURLSessionTask *lastSessionTask = [self.cancelTaskMDic objectForKey:url];
if (nil == lastSessionTask) {
[self.cancelTaskMDic setObject:task forKey:url];
return;
}
[lastSessionTask cancel];
}
- (void)clearTaskSessionWithUrl:(NSString *)url
{
[self.cancelTaskMDic removeObjectForKey:url];
}
#pragma mark - Remove Unused Things
#pragma mark - Private Methods
#pragma mark - Getter Methods
- (NSMutableDictionary *)requestTimeMDic
{
if (nil == _requestTimeMDic) {
_requestTimeMDic = [[NSMutableDictionary alloc] initWithCapacity:5];
}
return _requestTimeMDic;
}
- (NSMutableDictionary *)cancelTaskMDic
{
if (nil == _cancelTaskMDic) {
_cancelTaskMDic = [[NSMutableDictionary alloc] initWithCapacity:5];
}
return _cancelTaskMDic;
}
PGNetworkHelper
PGNetworkHelper - PINCache作为AFNetworking缓存层,将AFNetworking请求的数据缓存起来,支持取消当前网络请求,以及取消所有的网络请求,除了常用的Get,Post方法,也将上传图片以及下载文件进行了封装,同样支持同步请求,使用方法极其简单。