2016-04-01 13 views
0

私はBluetooth経由でExternalAccessoryと通信するアプリを持っています。応答が遅れているため、バックグラウンドスレッドでIOが発生します。私の要求をキューに入れるために、シングルスレッド動作のためのどのようにNSRunLoopをNSOperationQueueで使用しますか?

IセットアップNSOperationQueue:私はスケジュールする場合は、ソケットからのデータはできませんので

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

読み取り、そのキューからEAAccessoryストリームに書き込み、私のアプリがクラッシュキューが使用しているスレッドにNSRunLoopを付けずに配信できます。実行ループとしてキューが出ることはありませんが、私はどのようにわからないんだけど、このブロックを

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

を:すぐにキューを初期化した後、私は実行しているそれを維持し、それを起動するために、空のNSMachPortで実行ループを作成しますアクセサリーストリームから正常に読み取れるように、実行ループを正しく管理してください。

+0

」。しかし、実行ループを実行しているNSOperationブロックを有するNSOperationQueuesのスマートな使用ではありません「;同様に、単に(実行ループをそのまま実行している)NSOperationにすべてのスレッドコードを移動し、操作キューにそれをスローすることはありません。 -https://lists.apple.com/archives/cocoa-dev/2009/Sep/msg01145.html – alfwatt

+0

例:https://horseshoe7.wordpress.com/2015/04/29/nsoperation-and-nsrunloop- - 結婚・オブ・必要性/ – alfwatt

答えて

0

main任意のココアのスレッドまたはAppKitのアプリケーションは、デフォルトでは1回のランニングを持っており、どのセカンダリスレッドでもプログラムで実行する必要があります。 NSThreadを生成している場合、スレッド本体はNSRunLoopの起動を担当しますが、NSOperationQueueは自身のスレッドまたはスレッドを作成し、操作をディスパッチします。

NSRunLoopが、自分の作成のいずれか、またはlibdispatchが作成したバックグラウンドスレッドとイベントをやりとりすることを期待するAPIを使用する場合は、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をスケジュールすることができます。通常は、いくつかの条件があなたのNSBlockOperationタスクのそれぞれに満たされるまで、私はこれを簡略化しNSRunLoopのカテゴリを書いたループを実行することになるでしょうループと、指定した条件が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]; 
}]; 

シワ上記の例では、操作が完了すると、デリゲートがNOに設定することができBOOLのどこかに入れています。ここで

NSRunLoop+Conditionカテゴリの要旨です。実行ループを使用すると、APIコミットメントの通知または類似の種類を取得したいので、あなたは注意する必要が実行されている場合。あなたはちょうどそのループの実行を排除し、物事が働き続けることはできません

+0

これは100%CPUを使用していませんか? –

1

実行ループをNSOperationの内部で実行しないでください。 Grand Central Dispatchは、操作が実行されているスレッドを所有しています。独自のスレッドを起動し、セッションストリームにその実行ループを使用する必要があります。

However, you need to be aware that NSRunLoop is not generally thread safe, but CFRunLoop is.これは、あなたがあなたのセッションハンドリングスレッドでブロックを実行したいときCFRunLoopレベルまで低下する必要があることを意味します。

また、バックグラウンドスレッドの実行ループへの参照を取得する唯一の方法は、そのバックグラウンドスレッドで何かを実行することです。

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のインスタンスを作成することができますので、ステップ1は、独自の実行ループをエクスポートし、独自のNSThreadサブクラスを作成することです。 MyThreadを起動すると、そのコールバックがメインスレッドで実行され、コールバックに自身のループ(MyThread ')が渡されます。ですから、このようなセッションハンドリングスレッドとしてMyThreadを使用することができます。必要に応じていずれかのスレッドがそれのために作成したNSRunLoopを持つことができます

@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

これは、NSThreadからNSRunLoopにサービスを提供するための正しい答え(とよく書かれ1)ですが、私は、特にシリアルNSOperationQueuesでNSOperationsについて尋ねています。 – alfwatt

関連する問題