为什么说,你纯看代码而没有碰到这个场景你就算看懂了也没法理解?
我碰到了一个什么问题?
来说一个场景,打开你的Instagram,如果手机有VPN,请连接上VPN.打开搜索页面,连续输入m,i,k,e.
你会发现,小菊花转了4次.可以想象,客户端向服务端发起了4个请求,搜索的字段分别是"m","mi","mik","mike".
那么问题来了.这四次搜索肯定是并发的,也就是说,客户端同时向服务端发起了四次搜索请求,那么,怎么做到每次返回的结果总是最后一次输入的结果呢?
来看下面这段代码.
for
(int i = 1; i <= 10; i++) {
[Seller requestSellerWithCompletion:^(id object) {
NSLog(@
"finished download %d"
,i);
}];
}
这个requestSellerWithCompletion方法就是我封装了最简单的一个AFNetworkingOperation请求.
内容如下.
+ (void)requestSellerWithCompletion:(requestFinishedCompletionBlock)successBlock
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:kRequestSellerURL parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *sellerArray = [MTLJSONAdapter modelsOfClass:[Seller class] fromJSONArray:responseObject[@
"data"
] error:nil];
if
(successBlock) {
// NSLog(@"current operation count is %d",[manager.operationQueue operations].count);
successBlock(sellerArray);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
}
好,我们执行以下.console打出的结果如下.
对多线程稍有了解的都会知道这结果很正常.并发吗.试想一下,如果你在UITextField的delegate方法里直接对搜索字段执行搜索字段,那么服务端返回的结果肯定是有问题的,因为很有可能你输入的是mike,而返回的是"mi"的搜索结果.
所以,在上面那个for循环中,我们只需要最后一次执行结果.也就是i = 10的执行结果.前9次不管你是执行了也好,还是我中断了也好,我是不需要的.
那么怎么实现呢?
首先,需要否决的是想通过设置maxConcurrentOperationCount = 1解决问题的方案.
原因很简单,request的顺序执行并不能保证response返回也是顺序的.因为网速是时快时慢得.
而且maxConcurrentOperationCount这个参数实际上并不是干这个的.
他的适合的使用场景是为了它的主要意义就是控制连接数,2G网络下一次只能维持一个链接,3G是2个,
4G和wifi是不限。这个是对应协议的限制。如果超过这个限制发出的请求,就会报超时。
所以你以后封装httpClient的时候可能需要依据网络条件来设置你OperationQueue的这个参数值.
我们应该怎么做?
我又想到了另外一个办法,每次执行request的时候先清空OperationQueue里的所有operation.
也即调用[operationQueue cancelAllOperations]
其实这种想法暴露了我的iOS开发功底不扎实的问题.
来看看苹果文档对这个方法的描述.
Canceling the operations does not automatically remove them from the queue or stop those that are currently executing.
就是即便调用了这个方法也并不能移除正在执行的operation.所以,翻译过来就是,然并卵.
这条路已经死了.
到底应该怎么办?
首先NSOperation有一个Bool值,叫做cancel.这个Bool值并不能控制operation的执行和终止,他只是起一个标记作用.
Yes的时候就是说,我虽然在执行,但是我被cancel掉了.
那么我们每次执行request的时候都把上一个operationcancel掉,然后在completionBlock中判断operation是否cancel,如果cancel那么不返回response的值不就行了么.
我是这么改到.
+ (AFHTTPRequestOperation *)requestSellerWithCompletion:(requestFinishedCompletionBlock)successBlock
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
AFHTTPRequestOperation * operation = [manager GET:kRequestSellerURL parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if
(operation.isCancelled) {
NSLog(@
"operation is Canceled"
);
}
else
{
NSArray *sellerArray = [MTLJSONAdapter modelsOfClass:[Seller class] fromJSONArray:responseObject[@
"data"
] error:nil];
if
(successBlock) {
// NSLog(@"current operation count is %d",[manager.operationQueue operations].count);
successBlock(sellerArray);
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
return
operation;
}
然后在for循环里这么做.
for
(int i = 1; i <= 10; i++) {
if
(operation) {
[operation cancel];
}
operation = [Seller requestSellerWithCompletion:^(id object) {
NSLog(@
"finished download %d"
,i);
}];
}
运行一下看看console .
正确了.
学而不思则罔,思而不学则殆.
每个人要了解自己的优缺点.
为什么这么说呢?
像我写代码就比较喜欢实现,而不是探究底层.
以前我一直觉得写出特别精美的界面和酷炫的交互是一件特别屌的事情,其实这些东西在你基本功修炼的特别扎实对一些系统底层的实现特别了解的时候都是很自然而然就掌握的事情,而把大量时间花在这上面并不划算.你做的交互再屌,不还是用的CoreAnimation么.
所以我们应该把精力和时间投入在更值得学习的东西上.
共勉.