4

I have a loop function and in it called [NSThread sleepForTimeInterval:2.0];. it mean after 2s, loop function is called. I want when pass new view, this loop function is stop and when back, it is called again.

I use this code to call loop function:

-(void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    loop = YES;
    delete=NO;
    temp = [FileCompletedArray mutableCopy];
    NSOperationQueue *queue = [NSOperationQueue new];

    operations = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(updateArray) object:nil];
    [queue addOperation:operations];
    [operations release];

}

And loop function:

-(void)updateArray{

   while (loop)
   {
        NSLog(@"start loop");

       if(loop){
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"start send request");


        NSURL *url1 = [NSURL URLWithString:@"http://server.com"];


        NSMutableURLRequest *afRequest = [httpClient requestWithMethod:@"POST" path:nil parameters:params1] ;

        operation= [[AFHTTPRequestOperation alloc] initWithRequest:afRequest];
      NSLog(@" request sent");
        [operation  setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

            NSLog(@"Server response1");

        }
      failure:^(AFHTTPRequestOperation *operation, NSError *error) {
          NSLog(@"error: %@", error);
     }
         ];
        [httpClient enqueueHTTPRequestOperation:operation];
   }
       else
           return;
   }

}

And viewdisappear()

-(void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    loop = NO;
    delete=NO;
    [operations cancel] ;
}

My problem is when pass new view, updateArray still call. It not stop. Do you have suggestion?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
NGOT
  • 149
  • 5
  • 12

2 Answers2

2

You can try it using key value observers. You can implement the changes in following method which will be automatically called as a particular value changes. First you have to set the notification for the ivar that is going to be changed.

[self addObserver:self forKeyPath:@"loop" options:NSKeyValueObservingOptionNew context:nil];

Then you have to implement the changes as per requirement in the following method:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
user3228871
  • 103
  • 7
0

There are a couple of issues that leap out at me:

  1. If your intent is to actually cancel the sleepForTimeInterval, I don't believe you can do that. There are other mechanisms (such as timers) that are much better suited for that problem.

  2. As an aside, you are issuing an asynchronous request every two seconds. But you have no assurances that your previous request will have completed in that period of time, though. As a result, you can end up with a backlog of multiple network requests that will continue to run after the view is dismissed.

    I would have thought that you'd want to initiate the "wait two seconds" inside the completion block of your asynchronous request, to ensure your requests don't get backlogged behind your "every two seconds" logic. Clearly that won't work with your current while loop unless you make the request synchronous, so you might refactor this code, replacing the while loop with something that performs a single request, and in the completion block, waits two seconds before initiating the next request.

  3. You are checking the state of loop, waiting two seconds, and then issuing your request. So if the view disappeared while it was performing the two second sleep, there's nothing here stopping the request from being issued after you finished sleeping.

    At the very least, if you're going to use sleepForTimeInterval, you presumably want to check loop state after you finish sleeping. But, to my first point, it's better to use some cancelable mechanism, such as a timer.

  4. If you're saying that your loop never exits, I'd suggest you check to make sure the appearance methods are getting called like you think they should be.

Personally, I'd be inclined to do this with a timer which can easily be canceled/invalidated:

  1. Define my properties:

    @property (nonatomic, strong) NSTimer *timer;
    @property (nonatomic, getter = isLooping) BOOL looping;
    @property (nonatomic, weak) AFHTTPRequestOperation *operation;
    
  2. Have my appearance methods set the looping variable and start/stop the scheduled requests as appropriate:

    - (void)viewDidAppear:(BOOL)animated
    {
        self.looping = YES;
        [self scheduleRequestIfLooping];
    }
    
    - (void)viewWillDisappear:(BOOL)animated
    {
        self.looping = NO;
        [self cancelScheduledRequest];
    }
    
  3. The methods that do the starting and stopping of the scheduled requests would use the NSTimer:

    - (void)scheduleRequestIfLooping
    {
        if ([self isLooping]) {
            self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(initiateRequest:) userInfo:nil repeats:NO];
        }
    }
    
    - (void)cancelScheduledRequest
    {
        [self.timer invalidate];
        self.timer = nil;
    
        // you might want to cancel any `AFHTTPRequestOperation` 
        // currently in progress, too
    
        [self.operation cancel];
    }
    

    Note, whether the cancel method should cancel both the timer and any current request in progress (if any) is up to you.

  4. Finally, put the scheduling of the next request inside the completion block of the current request.

    - (void)initiateRequest:(NSTimer *)timer
    {
        // create AFHTTPRequestOperation
    
        AFHTTPRequestOperation operation = ...
    
        // now schedule next request _after_ this one, by initiating that request in the completion block
    
        [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
            NSLog(@"Server response1");
            [self scheduleRequestIfLooping];
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"error: %@", error);
            // not sure if you want to schedule request if last one failed
        }];
    
        self.operation = operation; // save this in the property
    }
    

In the above, I'm using the main thread for the timer. If you really want to do it on a background queue, you can, but (a) it seems unnecessary to me as the network requests already happen on a background thread; and (b) if you do this on a background thread, you might want to make sure you're doing the necessary thread-safe handling of your state variables and the like. It just seemed like an unnecessary complication to me.

Rob
  • 415,655
  • 72
  • 787
  • 1,044