2016-04-01 3 views
0

У меня есть приложение, которое общается с ExternalAccessory по Bluetooth, есть некоторая задержка в ответах, поэтому я хочу, чтобы IO произошел в фоновом потоке.Как использовать NSRunLoop для NSOperationQueue?

настроить соединение NSOperationQueue для однопоточных операций епдиеего моих запросов:

self.sessionQueue = [NSOperationQueue new]; 
self.sessionQueue.maxConcurrentOperationCount = 1; 

Если я планировать операции чтения и запись в EAAccessory потоки из этой очереди, мои приложение аварий, так как данные из сокета не могут доставляться без NSRunLoop в потоке, который использует очередь. Сразу после инициализации очереди, создать цикл выполнения с пустой NSMachPort, чтобы сохранить это работает и запустить его:

[self.sessionQueue addOperationWithBlock:^{ 
    NSRunLoop* queueLoop = [NSRunLoop currentRunLoop]; 
    [queueLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes]; 
    [queueLoop run]; // <- this blocks 
}]; 

Это блокирует очередь как цикл выполнения никогда не будет выход, но я не знаю, как правильно управлять циклом запуска, чтобы я мог успешно читать из дополнительных потоков.

+0

«Если цикл запуска запущен, потому что вы хотите получить уведомление или аналогичные обязательства API, тогда вам нужно быть осторожным. Вы не можете просто исключить работу этого цикла и заставить все работать. аналогичным образом не просто переместить весь код потока в NSOperation (с запущенным циклом запуска) и бросить его в очередь операций, а блок NSOperation, выполняющий цикл выполнения, не является разумным использованием NSOperationQueues ». -https: //lists.apple.com/archives/cocoa-dev/2009/Sep/msg01145.html – alfwatt

+0

Пример: https://horseshoe7.wordpress.com/2015/04/29/nsoperation-and-nsrunloop- a-marriage-of-need/ – alfwatt

ответ

0

Любого поток может иметь NSRunLoop, созданные для него, если необходимо, main нити любого применения какао или AppKit имеет бегущий по умолчанию и любые вторичные потоки должны запускать их программно. Если вы создавали NSThread, тело нити отвечало бы за запуск NSRunLoop, но NSOperationQueue создает собственный поток или потоки и отправляет им операции.

При использовании API, который ожидает NSRunLoop для доставки событий и от фонового потока, либо из вашего собственного создания, или тот, который libdispatch создал, вы несете ответственность за убедившись, что NSRunLoop запускается.Как правило, вы хотите, чтобы запустить цикл, пока некоторое условие не будет выполнено в каждом из ваших NSBlockOperation задач, я написал категорию на NSRunLoop что упрощает это:

#import <Foundation/Foundation.h> 

@interface NSRunLoop (Conditional) 
-(BOOL)runWhileCondition:(BOOL *)condition inMode:(NSString *)mode inIntervals:(NSTimeInterval) quantum; 
@end 

#pragma mark - 

@implementation NSRunLoop (Conditional) 

-(BOOL)runWhileCondition:(BOOL *)condition inMode:(NSString *)mode inIntervals:(NSTimeInterval) quantum { 
    BOOL didRun = NO; 
    BOOL shouldRun = YES; 
    NSPort *dummyPort = [NSMachPort port]; 
    [self addPort:dummyPort forMode:NSDefaultRunLoopMode]; 
    while (shouldRun) { 
     @autoreleasepool { 
      didRun = [self runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:quantum]]; 
      shouldRun = (didRun ? *condition : NO); 
     } 
    } 
    [self removePort:dummyPort forMode:NSDefaultRunLoopMode]; 
    return didRun; 
} 

@end 

При этом условии вы можете запланировать NSBlockOperation, который начнет запуск петли и работать до указанного условия является NO:

__block BOOL streamOperationInProgress = YES; 
[self.sessionQueue addOperationWithBlock:^{ 
    NSRunLoop *queueLoop = [NSRunLoop currentRunLoop]; 
    NSStream *someStream = // from somewhere... 
    [someStream setDelegate:self]; 
    [someStream scheduleInRunLoop:queueLoop forMode:NSDefaultRunLoopMode]: 

    // the delegate implementation of stream:handleEvent: 
    // sets streamOperationInProgress = NO; 

    [queueLoop 
     runWhileCondition:&streamOperationInProgress 
     inMode:NSDefaultRunLoopMode 
     inIntervals:0.001]; 
}]; 

морщинки в приведенном выше примере, помещая BOOL где-то, что делегат может установить его NO, когда операция будет завершена.

Вот сущность категории NSRunLoop+Condition.

+0

Разве это не использование 100% -ного процессора? –

1

Не следует пытаться запустить цикл запуска внутри NSOperation. Grand Central Dispatch владеет потоком, на котором выполняется операция. Вы должны начать свой собственный поток и использовать его цикл цикла для ваших потоков сеанса.

However, you need to be aware that NSRunLoop is not generally thread safe, but CFRunLoop is. Это означает, что вам нужно спуститься до уровня CFRunLoop, если вы хотите запустить блок в потоке обработки сеанса.

Кроме того, единственный способ получить ссылку на цикл выполнения фонового потока - это запустить что-то на этом фоновом потоке. Так шаг один, чтобы создать свой собственный NSThread подкласс, который экспортирует свой собственный цикл выполнения:

typedef void (^MyThreadStartCallback)(CFRunLoopRef runLoop); 

@interface MyThread: NSThread 

/// After I'm started, I dispatch to the main queue to call `callback`, 
// passing my runloop. Then I destroy my reference to `callback`. 
- (instancetype)initWithCallback:(MyThreadStartCallback)callback; 

@end 

@implementation MyThread { 
    MyThreadStartCallback _callback; 
} 

- (instancetype)initWithCallback:(MyThreadStartCallback)callback { 
    if (self = [super init]) { 
     _callback = callback; 
    } 
    return self; 
} 

- (void)main { 
    CFRunLoopRef runLoop = CFRunLoopGetCurrent(); 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     _callback(runLoop); 
    }); 
    _callback = nil; 
    CFRunLoopRun(); 
} 

@end 

Теперь вы можете создать экземпляр MyThread, переходящий в обратном вызове. Когда вы запустите MyThread, он заставит обратный вызов вернуться к основному потоку и передаст свой обратный вызов свой собственный (MyThread) цикл. Таким образом, вы можете использовать MyThread в качестве вашей сеанса обработки резьбы, например:

@implementation Thing { 
    CFRunLoopRef _sessionRunLoop; 
} 

- (void)scheduleStreamsOfSession:(EASession *)session { 
    MyThread *thread = [[MyThread alloc] initWithCallback:^(CFRunLoopRef runLoop) { 
     // Here I'm on the main thread, but the session-handling thread has 
     // started running and its run loop is `runLoop`. 
     [self scheduleStreamsOfSession:session inRunLoop:runLoop]; 
    }]; 
    [thread start]; 
} 

- (void)scheduleStreamsOfSession:(EASession *)session inRunLoop:(CFRunLoopRef)runLoop { 

    // Here I'm on the main thread. I'll save away the session-handling run loop 
    // so I can run more blocks on it later, perhaps to queue data for writing 
    // to the output stream. 
    _sessionRunLoop = runLoop; 

    NSInputStream *inputStream = session.inputStream; 
    NSOutputStream *outputStream = session.outputStream; 

    // Here I'm on the main thread, where it's not safe to use the 
    // session-handling thread's NSRunLoop, so I'll send a block to 
    // the session-handling thread. 
    CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{ 

     // Here I'm on the session-handling thread, where it's safe to 
     // use NSRunLoop to schedule the streams. 
     NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; 
     [inputStream scheduleInRunLoop:currentRunLoop forMode:NSRunLoopCommonModes]; 
     [outputStream scheduleInRunLoop:currentRunLoop forMode:NSRunLoopCommonModes]; 

    }); 

    // CFRunLoopPerformBlock does **not** wake up the run loop. Since I want 
    // to make sure the block runs as soon as possible, I have to wake up the 
    // run loop manually: 
    CFRunLoopWakeUp(_sessionRunLoop); 
} 

@end 
+0

Это правильный ответ (и хорошо написанный) для обслуживания NSRunLoop из NSThread, но я спрашиваю о NSOperations в серийном NSOperationQueues специально. – alfwatt

Смежные вопросы