Search code examples
iosnsthreadnsrunloopviewdidappear

"[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]" makes the UI non response


In the UIViewController viewDidAppear event, I want to get some data from web service. And the code like:

- (void)viewDidAppear:(BOOL)animated
{
 [super viewDidAppear:animated];
 NSArray *arr = [self getCarList];
}

- (NSArray *)getCarList
{
if (!carList) {

    ARequset *req = [[ARequset alloc] init];
    [NetService sendRequest:req respClass:[Resp class] success:^(BaseResponse *response)
    {
        //after finished
        self.requestFinished = YES;
    } fail:^(NSInteger errcode, NSString *errmsg) {
        self.requestFinished = YES;
    }];
    while (!self.requestFinished) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
}
 return carList;
}

when run in to [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; the request success block will not be performed, and the UI become no response.

but if i change the - (void)viewDidAppear:(BOOL)animated like this, all goes well.

 - (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  [self performSelector:@selector(getCarList) withObject:self afterDelay:1];
}

Solution

  • Don't do that kind of syntax

    The NetService request works asynchronously, the result of the request is passed in the block. You have to process the BaseResponse object and update your UI or whatever you want to do with the data.

    Polling is bad habit and consumes system resources unnecessarily.

    Apart from that you're telling the currently running runloop of UIApplication to run which blocks it.

    Do something like this

    - (void)getCarList
    {
    if (!carList) {
    
        ARequset *req = [[ARequset alloc] init];
        [NetService sendRequest:req respClass:[Resp class] success:^(BaseResponse *response)
        {
            //after finished
            self.carList = [self doSomethingWithTheResponse:response];
            dispatch_async(dispatch_get_main_queue(), ^(void) {
            // Update UI
            });
    
        } fail:^(NSInteger errcode, NSString *errmsg) {
            dispatch_async(dispatch_get_main_queue(), ^(void) {
            // show alert
            });
          }];
        }
      }
    }
    

    Edit: Alternatively use a delegate like pattern to handle the asynchronous behavior.

    Instead of the synchronous method

    - (void)methodToGetCarList
    {
      NSArray *cars = [self getCarList];
      [self doSomethingWithCars:cars];
    }
    

    use this

    - (void)methodToGetCarListAsynchronously
    {
      [self getCarList];
    }
    

    and the delegate method

    - (void)didReceiveCars:(NSArray *)cars errorMessage:(NSString *)error
    {
      if (error) {
        // do error handling
      } else {
        [self doSomethingWithCars:cars];
      }
    }
    

    the getCarList method looks like

    - (void)getCarList
    {
      if (!carList) {
    
        ARequset *req = [[ARequset alloc] init];
        [NetService sendRequest:req respClass:[Resp class] success:^(BaseResponse *response)
        {
            //after finished
            self.carList = [self doSomethingWithTheResponse:response];
            [self didReceiveCars:self.carList errorMessage:nil];
    
        } fail:^(NSInteger errcode, NSString *errmsg) {
           [self didReceiveCars:nil errorMessage:errmsg];
          }];
        }
      } else {
        [self didReceiveCars:self.carList errorMessage:nil];
      }
    }
    

    The code does not consider potential issues if the response returns in a background thread.