我有一个通过蓝牙与 ExternalAccessory 通信的应用,响应有一些延迟,所以我希望 IO 在后台线程上发生。
我为单线程操作设置了一个 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
}];
这会阻塞队列,因为运行循环永远不会退出,但我不确定如何正确管理运行循环,以便我可以成功地从附属流中读取。
Best Answer-推荐答案 strong>
您不应该尝试在 NSOperation 中运行运行循环。 Grand Central Dispatch 拥有运行该操作的线程。您应该启动自己的线程并将其运行循环用于 session 流。
However, you need to be aware that NSRunLoop is not generally thread safe, but CFRunLoop is.这意味着当您想在 session 处理线程上运行一个 block 时,您需要下拉到 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)initWithCallbackMyThreadStartCallback)callback;
@end
@implementation MyThread {
MyThreadStartCallback _callback;
}
- (instancetype)initWithCallbackMyThreadStartCallback)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 作为 session 处理线程,如下所示:
@implementation Thing {
CFRunLoopRef _sessionRunLoop;
}
- (void)scheduleStreamsOfSessionEASession *)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)scheduleStreamsOfSessionEASession *)session inRunLoopCFRunLoopRef)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
关于ios - 如何在 NSOperationQueue 上使用 NSRunLoop?,我们在Stack Overflow上找到一个类似的问题:
https://stackoverflow.com/questions/36364179/
|