如何在NSOperation中实现一个NSRunLoop

我张贴这个问题,因为我已经看到了很多关于这个话题的困惑,因此我花了几个小时debuggingNSOperation子类。

问题是,当你执行asynchronous方法时,NSOperation不会对你有太大的帮助,在asynchronouscallback完成之前,这些方法实际上并不完整。

如果NSOperation本身就是callback委托,那么由于在不同线程上发生的callback,可能不足以正确完成操作。

比方说,你在主线程,你创build一个NSOperation并将其添加到一个NSOperationQueue NSOperation内的代码触发一个asynchronous调用,调用回AppDelegate或视图控制器上的某些方法。

你不能阻止主线程或用户界面将locking,所以你有两个select。

1)创build一个NSOperation并将其添加到NSOperationQueue中,签名如下:

[NSOperationQueue addOperations:@ [myOp] waitUntilFinished:?]

祝你好运。 asynchronous操作通常需要一个runloop,所以它不会工作,除非你inheritanceNSOperation或者使用一个块,但是如果你必须在callback完成的时候告诉它“完成”NSOperation,那么块就不会工作。

所以…你用类似于下面的东西来NSOperation的子类,所以callback可以告诉操作完成后:

//you create an NSOperation subclass it includes a main method that //keeps the runloop going as follows //your NSOperation subclass has a BOOL field called "complete" -(void) main { NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; //I do some stuff which has async callbacks to the appDelegate or any other class (very common) while (!complete && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); } //I also have a setter that the callback method can call on this operation to //tell the operation that its done, //so it completes, ends the runLoop and ends the operation -(void) setComplete { complete = true; } //I override isFinished so that observers can see when Im done // - since my "complete" field is local to my instance -(BOOL) isFinished { return complete; } 

好的 – 这绝对不行 – 我们就这么做了!

2)这个方法的第二个问题是让我们说在runLoops必须正确终止的情况下(或实际上终止于callback中的外部方法调用)上面的实际工作(它不这样做)

让我们假设在主线程中的第二个Im时,除非我想要UIlocking了一下,而不画任何东西,我不能说NSOperationQueue addOperation方法的“waitUntilFinished:YES”…

那么,如何在不locking主线程的情况下完成与waitUntilFinished:YES相同的行为?

由于在Cocoa中runLoops,NSOperationQueues和Asynch行为有很多问题,所以我会发布我的解决scheme作为这个问题的答案。

请注意,我只回答我自己的问题,因为我检查meta.stackoverflow,他们说这是可以接受和鼓励,我希望下面的答案帮助人们了解为什么他们的runloops在NSOperationslocking,以及他们如何能够从外部正确地完成NSOperationscallback。 (在其他线程上的callback)

问题1的答案

我有一个NSOperation在其主要方法中调用asynchronous操作,该操作在操作之外callback,我需要告诉操作完成并结束NSOperation:

以下代码从上面修改

 //you create an NSOperation subclass it includes a main method that //keeps the runloop going as follows //your NSOperation subclass has a BOOL field called "complete" //ADDED: your NSOperation subclass has a BOOL field called "stopRunLoop" //ADDED: your NSOperation subclass has a NSThread * field called "myThread" -(void) main { myThread = [NSThread currentThread]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; //I do some stuff which has async callbacks to the appDelegate or any other class (very common) while (!stopRunLoop && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); //in an NSOperation another thread cannot set complete //even with a method call to the operation //this is needed or the thread that actually invoked main and //KVO observation will not see the value change //Also you may need to do post processing before setting complete. //if you just set complete on the thread anything after the //runloop will not be executed. //make sure you are actually done. complete = YES; } -(void) internalComplete { stopRunloop = YES; } //This is needed to stop the runLoop, //just setting the value from another thread will not work, //since the thread that created the NSOperation subclass //copied the member fields to the //stack of the thread that ran the main() method. -(void) setComplete { [self performSelector:@selector(internalComplete) onThread:myThread withObject:nil waitUntilDone:NO]; } //override isFinished same as before -(BOOL) isFinished { return complete; } 

问题#2的答案 – 你不能使用

 [NSOperationQueue addOperations:.. waitUntilFinished:YES] 

因为你的主线程不会更新,但是你也有几个OTHER操作,在NS操作完成之前不能执行,而且它们中的任何一个都不应该阻塞主线程。

input…

 dispatch_semaphore_t 

如果你有几个你需要从主线程启动的依赖NSOperations,你可以传递一个调度信号给NSOperation, 记住这些是NSOperation主方法内的asynchronous调用 ,所以NSOperation子类需要等待这些callback完成。 从callback方法链也可能是一个问题。

通过从主线程传入信号量,您可以使用[NSOperation addOperations:… waitUntilFinished:NO],并仍然阻止其他操作执行,直到您的callback完成。

创buildNSOperation的主线程的代码

 //only one operation will run at a time dispatch_semaphore_t mySemaphore = dispatch_semaphore_create(1); //pass your semaphore into the NSOperation on creation myOperation = [[YourCustomNSOperation alloc] initWithSemaphore:mySemaphore] autorelease]; //call the operation [myOperationQueue addOperations:@[myOperation] waitUntilFinished:NO]; 

… NSOperation代码

 //In the main method of your Custom NSOperation - (As shown above) add this call before //your method does anything //my custom NSOperation subclass has a field of type dispatch_semaphore_t //named "mySemaphore" -(void) main { myThread = [NSThread currentThread]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; //grab the semaphore or wait until its available dispatch_semaphore_wait(mySemaphore, DISPATCH_TIME_FOREVER); //I do some stuff which has async callbacks to the appDelegate or any other class (very common) while (!stopRunLoop && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); //release the semaphore dispatch_semaphore_signal(mySemaphore); complete = YES; } 

当你的另一个线程调用setComplete方法在NSOperation 3上的事情会发生,

  1. runloop将停止允许NSOperation完成(否则它不会)

  2. 信号量将被释放,允许共享信号量的其他操作运行

  3. NSOperation将完成和交易

如果使用方法2,则可以等待从NSOperationQueue调用的任意asynchronous方法,知道它们将完成runloop,并且可以以任何您喜欢的方式链接callback,而不会阻塞主线程。

我没有详细阅读这些答案,因为这些方法太复杂了; b)没有按照devise的方式使用NSOperation。 你们似乎是黑客已经存在的function。

解决办法是子类化NSOperation并重写getConfurrent返回YES。 然后执行 – (void)start方法并开始asynchronous任务。 然后您负责完成它,这意味着您必须在isFinished和isExecuting上生成KVO通知,以便NSOperationQueue可以知道任务已完成。

(更新:这是你如何子类NSOperation)(更新2:添加如何处理NSRunLoop,如果你有一个代码,需要一个在后台线程工作。例如Dropbox核心API)

 // HSConcurrentOperation : NSOperation #import "HSConcurrentOperation.h" @interface HSConcurrentOperation() { @protected BOOL _isExecuting; BOOL _isFinished; // if you need run loops (eg for libraries with delegate callbacks that require a run loop) BOOL _requiresRunLoop; NSTimer *_keepAliveTimer; // a NSRunLoop needs a source input or timer for its run method to do anything. BOOL _stopRunLoop; } @end @implementation HSConcurrentOperation - (instancetype)init { self = [super init]; if (self) { _isExecuting = NO; _isFinished = NO; } return self; } - (BOOL)isConcurrent { return YES; } - (BOOL)isExecuting { return _isExecuting; } - (BOOL)isFinished { return _isFinished; } - (void)start { [self willChangeValueForKey:@"isExecuting"]; NSLog(@"BEGINNING: %@", self.description); _isExecuting = YES; [self didChangeValueForKey:@"isExecuting"]; _requiresRunLoop = YES; // depends on your situation. if(_requiresRunLoop) { NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; // run loops don't run if they don't have input sources or timers on them. So we add a timer that we never intend to fire and remove him later. _keepAliveTimer = [NSTimer timerWithTimeInterval:CGFLOAT_MAX target:self selector:@selector(timeout:) userInfo:nil repeats:nil]; [runLoop addTimer:_keepAliveTimer forMode:NSDefaultRunLoopMode]; [self doWork]; NSTimeInterval updateInterval = 0.1f; NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:updateInterval]; while (!_stopRunLoop && [runLoop runMode: NSDefaultRunLoopMode beforeDate:loopUntil]) { loopUntil = [NSDate dateWithTimeIntervalSinceNow:updateInterval]; } } else { [self doWork]; } } - (void)timeout:(NSTimer*)timer { // this method should never get called. [self finishDoingWork]; } - (void)doWork { // do whatever stuff you need to do on a background thread. // Make network calls, asynchronous stuff, call other methods, etc. // and whenever the work is done, success or fail, whatever // be sure to call finishDoingWork. [self finishDoingWork]; } - (void)finishDoingWork { if(_requiresRunLoop) { // this removes (presumably still the only) timer from the NSRunLoop [_keepAliveTimer invalidate]; _keepAliveTimer = nil; // and this will kill the while loop in the start method _stopRunLoop = YES; } [self finish]; } - (void)finish { // generate the KVO necessary for the queue to remove him [self willChangeValueForKey:@"isExecuting"]; [self willChangeValueForKey:@"isFinished"]; _isExecuting = NO; _isFinished = YES; [self didChangeValueForKey:@"isExecuting"]; [self didChangeValueForKey:@"isFinished"]; } @end 

我不知道为什么你会希望所有的NSOperation的开销只是为了一个运行循环,但我想如果你正在使用一个操作队列devise,那么也许它是有用的。 我这样说的原因通常是你只需在后台执行一个select器,然后从那里调用CFRunLoopRun。

除此之外,下面是一个使用运行循环的示例NSOperation子类。 只需将其子类化并覆盖willRun并调用需要运行循环的方法即可。 一旦所有被调用的方法都完成了,所有运行的循环源都被处理了 – 操作将自动结束。 您可以通过在willRun方法中延迟一个简单的执行select器并在completeOperation中放置一个断点来testing它,并且您将看到只要执行完该操作就会持续​​运行。 而且,如果你在延迟时间之后执行其他的操作,那么操作将继续运行。 正如我所说,只要有一些需要运行循环的function,即使这些function是在启动之后添加的,它仍会继续运行。

不需要停止方法,因为只要一切都完成了,没有更多的来源可以处理,它将自动结束。

MHRunLoopOperation.h

 #import <Foundation/Foundation.h> @interface MHRunLoopOperation : NSOperation // Override and call methods that require a run loop. // No need to call super because the default implementation does nothing. -(void)willRun; @end 

MHRunLoopOperation.m

 #import "MHRunLoopOperation.h" @interface MHRunLoopOperation() @property (nonatomic, assign) BOOL isExecuting; @property (nonatomic, assign) BOOL isFinished; @end @implementation MHRunLoopOperation - (BOOL)isAsynchronous { return YES; } - (void)start { // Always check for cancellation before launching the task. if (self.isCancelled) { // Must move the operation to the finished state if it is canceled. self.isFinished = YES; return; } // If the operation is not canceled, begin executing the task. [self willChangeValueForKey:@"isExecuting"]; [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil]; _isExecuting = YES; [self didChangeValueForKey:@"isExecuting"]; } - (void)main { @try { // Do the main work of the operation here. [self willRun]; CFRunLoopRun(); // It waits here until all method calls or remote data requests that required a run loop have finished. And after that then it continues. [self completeOperation]; } @catch(...) { // Do not rethrow exceptions. } } -(void)willRun{ // To be overridden by a subclass and this is where calls that require a run loop are done, eg remote data requests are started. } -(void)completeOperation{ [self willChangeValueForKey:@"isFinished"]; [self willChangeValueForKey:@"isExecuting"]; _isExecuting = NO; _isFinished = YES; [self didChangeValueForKey:@"isExecuting"]; [self didChangeValueForKey:@"isFinished"]; } @end 

到底是什么,这里也是一个例子子类:-)

 @interface TestLoop : MHRunLoopOperation @end @implementation TestLoop // override -(void)willRun{ [self performSelector:@selector(test) withObject:nil afterDelay:2]; } -(void)test{ NSLog(@"test"); // uncomment below to make keep it running forever //[self performSelector:@selector(test) withObject:nil afterDelay:2]; } // overridden just for demonstration purposes -(void)completeOperation{ NSLog(@"completeOperation"); [super completeOperation]; } @end 

只需要像这样testing一下:

 TestLoop* t = [[TestLoop alloc] init]; [t start];