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

iOS开发中如遇到频繁的Http请求,如何取消之前已经发送的Http请求?_第1张图片





你可能感兴趣的:(Http网络相关)