您不應該嘗試在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
「如果因爲要獲取通知或類似的API承諾而運行循環,那麼您需要小心,不能僅僅消除該循環的運行並讓事情繼續工作。同樣,不要將所有線程代碼移動到NSOperation中(運行循環完好無損地運行)並將其放入操作隊列中;讓NSOperation塊運行運行循環並不是NSOperationQueues的明智之舉。「 -https://lists.apple.com/archives/cocoa-dev/2009/Sep/msg01145.html – alfwatt
示例:https://horseshoe7.wordpress.com/2015/04/29/nsoperation-and-nsrunloop-結婚的必要/ – alfwatt