Pregunta

How to wait inside the NSThread until some event occur in iOS?

eg, We created a NSThread and started a thread loop. Inside the thread loop, there is condition to check whether the message queue has any messages. If there is a message, then it will call the corresponding method to do some operation, else it should wait until the message queue gets populated with a new message.

Is there any API or methods available to wait until some event occur?

For Example 

NSThread *thread = [NSThread alloc]....@selector(threadLoop)

- (void)threadLoop
{
   // Expecting some API or method that wait until some messages pushed into the message queue
   if (...) {

   }
}

Any help should be appreciated.

¿Fue útil?

Solución

You can use NSCondition. I attach example code "ready-for-test" in a ViewController

@interface ViewController ()

@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSThread *aThread;

// use this property to indicate that you want to lock _aThread
@property (nonatomic) BOOL lock;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // start with the thread locked, update the boolean var
    self.lock = YES;

    // create the NSCondition instance
    self.condition = [[NSCondition alloc]init];

    // create the thread and start
    self.aThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadLoop) object:nil];
    [self.aThread start];

}

-(void)threadLoop
{
    while([[NSThread currentThread] isCancelled] == NO)
    {
        [self.condition lock];
        while(self.lock)
        {
            NSLog(@"Will Wait");
            [self.condition wait];

            // the "did wait" will be printed only when you have signaled the condition change in the sendNewEvent method
            NSLog(@"Did Wait");
        }

        // read your event from your event queue
        ...


        // lock the condition again
        self.lock = YES;
        [self.condition unlock];
    }

}

- (IBAction)sendNewEvent:(id)sender {
    [self.condition lock];
    // put the event in the queue
    ...


    self.lock = NO;
    [self.condition signal];
    [self.condition unlock];
}

Otros consejos

You could use run loop sources. In essence:

1) On secondary worker thread create and install run loop source, and pass it somehow, along with worker thread run loop reference, to other managing thread which will be sending messages to this one:

    CFRunLoopSourceContext context = {0, self, NULL, NULL, NULL, NULL, NULL,
                                    &RunLoopSourceScheduleRoutine,
                                    RunLoopSourceCancelRoutine,
                                    RunLoopSourcePerformRoutine};
    CFRunLoopSourceRef runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context);
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
    // Pass runLoopSource and runLoop to managing thread

Here there are custom routines mentioned above - you are responsible to provide them:

    RunLoopSourceScheduleRoutine - called when you install run loop source (more precisely, when you call CFRunLoopAddSource)

    RunLoopSourceCancelRoutine - called when you remove run loop source (more precisely, when you call CFRunLoopSourceInvalidate)

    RunLoopSourcePerformRoutine - called when run loop source was signaled (received a message from manager thread) and this is a place where you should perform a job

2) On worker thread, start usual run loop, something similar to this:

    BOOL done = NO;
    do {
        int result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
        done = (result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished);
    } while (!done); 

3) Now, on managing thread you could signal (send message) to previously received run loop source when needed (and wake up the run loop of those thread in case it is asleep):

    CFRunLoopSourceSignal(runLoopSource);
    CFRunLoopWakeUp(workerThreadRunLoop);

More details are in Apple's guide.

You can use a semaphore. See the example below, the logic is pretty simply. Im my example, the blocks are executed in background, and my main thread waits for the dispatch signal of the semaphore to go on. The main difference is in my case the thread waiting is the main thread, but the semaphore logic is here, I think you can easily adapt this to your case.

//create the semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

[objectManager.HTTPClient deletePath:[address addressURL] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {

      //some code here

        dispatch_semaphore_signal(semaphore);

    }failure:^(AFHTTPRequestOperation *operation, NSError *error) {

       //some other code here

        dispatch_semaphore_signal(semaphore);
    }];

//holds the thread until the dispatch_semaphore_signal(semaphore); is send
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW))
{
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top