我有一个TextField,无论什么时候当我输入字符的时候,我appending这个字符到我的url,然后发送一次请求,我现在需要取消之前的请求。例如当我输入“shampoo”的时候,我会触发7次代理方法,也就是我会触发七次不同的网络请求,那么有一个问题,这七次发出的请求,响应的顺序可不是你想的按顺序返回的,例如发送的是1234567,那么返回数据很有可能是1234576,这样导致最后需要的结果不是“7”,而是“6”。那么看看我是如何解决的以及遇到坑的!!!
1.第一种解决方案(失败)
[NSObject cancelPreviousPerformRequestsWithTarget:self];
我看到了这个方法,最终测试的结果是,它只能取消掉还未进行网络请求的方法 如下所示
[selfperformSelector:@selector(startSearchRefresh)withObject:nilafterDelay:0.5];
在这个delay的0.5秒以内,如果我再次触发该方法,那么,第一个方法就会取消掉之前还在delay的方法,从而达到目的但是如果到了0.5秒,方法已经进行http请求了呢,已经在请求的过程中了呢,这个方法是根本取消不掉的。
因此,这个方法可以用于用户快速输入的时候后一次操作覆盖前一次操作,对我来说还是失败的方法
2.第二种解决方案(失败)
当我进行网络请求的时候
[manager.operationQueue cancelAllOperations];
获取到AFNetWorking的对象的时候,拿到它的任务队列,然后取消掉之前所有的任务,我不知道是不是版本的问题,最新的AF(3.0)以上貌似根本没什么用,也有可能是我的插入方式有问题吧
来看看一个老外的实时搜索的代码段,根本没取消掉
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;
{
//[self.s_searchResultText setHidden:YES];
// [SVProgressHUD dismiss];
[self.s_tableView setHidden:true];
[searchProductArray removeAllObjects];
[self.s_tableView reloadData];
NSCharacterSet *cs = [[NSCharacterSet characterSetWithCharactersInString:UNACCEPTABLE_CHARACTERS] invertedSet];
NSLog(@"%@",cs);
NSString *filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""];
NSLog(@"%@",filtered);
NSLog(@"%lu",(unsigned long)filtered.length);
if (filtered.length) {
[CustomToastAlert showToastInParentView:self.view withText:@"Please enter valid characters" withDuaration:1.5];
return NO;
}
searchTextString = [textField.text stringByAppendingString:string];
NSLog(@"%lu",(unsigned long)[searchTextString length]);
NSLog(@"%@",searchTextString);
int stringLength=[searchTextString length];
const char * _char = [string cStringUsingEncoding:NSUTF8StringEncoding];
int isBackSpace = strcmp(_char, "\b");
if (isBackSpace == -8) {
stringLength=[searchTextString length];
stringLength=[searchTextString length]-1;
searchTextString=[searchTextString substringToIndex:stringLength];
NSLog(@"Backspace was pressed");
NSLog(@"string is %@",searchTextString);
}
if(stringLength>=3)
{
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
NSString *urlString=[NSString stringWithFormat:kSearchProductUrl,kBaseUrl];
urlString = [urlString stringByAppendingString:searchTextString];
urlString = [urlString stringByReplacingOccurrencesOfString:@" " withString:@"%20"];
NSLog(@"%@",searchTextString);
NSLog(@"%@",urlString);
[searchProductArray removeAllObjects];
// [manager invalidateSessionCancelingTasks:NO];
//[manager.operationQueue cancelAllOperations];
[manager GET:urlString parameters:nil success:^(NSURLSessionDataTask *task, id responseObject)
{
[searchProductArray removeAllObjects];
//[SVProgressHUD showWithStatus:LOADING_ITEMS maskType:SVProgressHUDMaskTypeGradient];
NSLog(@"JSON: %@", responseObject);
json= responseObject;
NSLog(@"%@",json);
NSLog(@"%lu",(unsigned long)[[json valueForKeyPath:@"data"] count ]);
for(int i=0;i<[[json valueForKeyPath:@"data"]count ];i++)
{
Product *s_productList=[[Product alloc]init];
s_productList.SKU_Name=[[json valueForKeyPath:@"data.sku_name"]objectAtIndex:i];
s_productList.SKU_Id=[[json valueForKeyPath:@"data.sku_id"]objectAtIndex:i];
s_productList.SKU_Price=[[json valueForKeyPath:@"data.sku_price"]objectAtIndex:i];
s_productList.SKU_OfferPrice=[[json valueForKeyPath:@"data.sku_offer_price"]objectAtIndex:i];
s_productList.SKU_Currency = RUPEE_SYMBOL;
s_productList.SKU_AvailableUnit=[[json valueForKeyPath:@"data.sku_available_unit"]objectAtIndex:i];
s_productList.SKU_OfferDescription= [[json valueForKeyPath:@"data.sku_offer_desc"]objectAtIndex:i];
s_productList.SKU_ImageUrls=[[json valueForKeyPath:@"data.sku_image_urls"]objectAtIndex:i];
[searchProductArray addObject:s_productList];
NSLog(@"%lu",(unsigned long)[searchProductArray count]);
}
[self.s_tableView setHidden:FALSE];
[self.s_tableView reloadData];
NSLog(@"%lu",(unsigned long)[searchProductArray count]);
if ([searchProductArray count]==0) {
[CustomToastAlert showToastInParentView:self.view withText:SEARCH_RESULT withDuaration:1.5];
}
}
failure:^(NSURLSessionDataTask *task, NSError *error)
{
[CustomToastAlert showToastInParentView:self.view withText:NO_DATA_AVAIL withDuaration:1.5];
}];
}
return YES;
}
该方法也是从stackOverFlow上找来的,真的是找死我了
以上面的代码段为例,他是这么操作的
主要精髓在于
第一点:不要initialize a new AFHTTPSessionManager object everytime 一定要把manager用成全局的
第二点:把请求返回的task对象丢进数组,下次触发的时候把遍历数组,把之前的所有任务[task cancel]
// somewhere in your class, let's say in ViewDidLoad you should init the AFHTTPSessionManager object
- (void)viewDidLoad {
[super viewDidLoad];
/// create the AFHTTPSessionManager object, we're gonna use it in every request
self.manager = [[AFHTTPSessionManager alloc] init];
self.manager.responseSerializer = [AFJSONResponseSerializer serializer];
/// create an array that is going to hold the requests task we've sent to the server. so we can get back to them later
self.arrayOfTasks = [NSMutableArray new];
/// discussion:
/// an array holds multiple objects. if you just want to hold a ref to the latest task object
/// then create a property of NSURLSessionDataTask instead of NSMutableArray, and let it point to the latest NSURLSessionDataTask object you create
}
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;{
/// your code goes here
/// .....
/// .....
/// .....
/// .....
/// till we reach
if(stringLength>=3){
/// cancel all previous tasks
[self.arrayOfTasks enumerateObjectsUsingBlock:^(NSURLSessionDataTask *taskObj, NSUInteger idx, BOOL *stop) {
[taskObj cancel]; /// when sending cancel to the task failure: block is going to be called
}];
/// empty the arraOfTasks
[self.arrayOfTasks removeAllObjects];
/// init new task
NSURLSessionDataTask *task = [self.manager GET:urlString parameters:nil success:^(NSURLSessionDataTask *task, id responseObject){
/// your code
}failure:^(NSURLSessionDataTask *task, NSError *error){
/// your code
}];
/// add the task to our arrayOfTasks
[self.arrayOfTasks addObject:task];
}
return YES;
}
注意:
这里的task cancel亲测确实能把网络请求cancel掉,可以看下面的log,记住一点,这里cancel之后不代表就不回调了,只是会回调到error的那个block里面,各位需要信息的可以测试下,在error打个断点就能调试出来了
// 查看网络任务的状态
[articleInterface.articleArrayTask enumerateObjectsUsingBlock:^(NSURLSessionDataTask *taskObj, NSUInteger idx, BOOL *stop) {
DDLogVerbose(@"当前的文章删除前网络任务状态是==================>>>>>%ld",taskObj.state);
[taskObj cancel]; /// when sending cancel to the task failure: block is going to be called
DDLogVerbose(@"当前的文章删除后网络任务状态是==================>>>>>%ld",taskObj.state);
}];
typedef NS_ENUM(NSInteger, NSURLSessionTaskState) {
NSURLSessionTaskStateRunning = 0, /* The task is currently being serviced by the session */
NSURLSessionTaskStateSuspended = 1,
NSURLSessionTaskStateCanceling = 2, /* The task has been told to cancel. The session will receive a URLSession:task:didCompleteWithError: message. */
NSURLSessionTaskStateCompleted = 3, /* The task has completed and the session will receive no more delegate notifications */
} NS_ENUM_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0);
打印日志 这里的2代表NSURLSessionTaskStateCanceling