iOS開發中如遇到頻繁的Http請求,如何取消之前已經發送的Http請求?
我有一個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; }
3.第三種解決方法,這個才是我最終的解決方案
該方法也是從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