Objective c 如何在NSO操作中实现nsrunlop

Objective c 如何在NSO操作中实现nsrunlop,objective-c,ios,objective-c-blocks,objective-c-runtime,Objective C,Ios,Objective C Blocks,Objective C Runtime,我之所以发布这个问题,是因为我在这个主题上看到了很多困惑,因此我花了几个小时调试NSOperation子类 问题是,当您执行异步方法时,NSOperation对您没有多大帮助,而异步方法在异步回调完成之前实际上是不完整的 如果NSOperation本身是回调委托,那么由于回调发生在不同的线程上,它甚至可能不足以正确完成操作 假设您在主线程中,创建了一个NSOperation并将其添加到NSOperationQueue中。NSOperation中的代码会触发一个异步调用,该调用会回调AppDele

我之所以发布这个问题,是因为我在这个主题上看到了很多困惑,因此我花了几个小时调试NSOperation子类

问题是,当您执行异步方法时,NSOperation对您没有多大帮助,而异步方法在异步回调完成之前实际上是不完整的

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

假设您在主线程中,创建了一个NSOperation并将其添加到NSOperationQueue中。NSOperation中的代码会触发一个异步调用,该调用会回调AppDelegate或视图控制器上的某个方法

您不能阻止主线程,否则UI将锁定,因此您有两个选项

1)创建一个NSOperation并使用以下签名将其添加到NSOperationQueue中:

[NSOperationQueue addOperations:@[myOp]等待完成:?]

祝你好运。异步操作通常需要一个runloop,因此除非您将NSOperation子类化或使用一个块,否则它不会工作,但如果您必须在回调完成时告诉它,以“完成”NSOperation,则即使是块也不会工作

因此…您可以使用类似于以下内容的子类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"

-(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)此方法的第二个问题是,如果运行循环必须正确终止(或实际上从回调中的外部方法调用终止),那么上述方法实际上可以工作(但实际上不能工作)

当我调用此函数时,假设主线程中有第二个Im,除非我希望UI锁定一点,而不是绘制任何内容,否则我不能在NSOperationQueue addOperation方法上说“waitUntilFinished:YES”

那么如何在不锁定主线程的情况下实现与waitUntilFinished:YES相同的行为呢?

由于Cocoa中有很多关于runLoops、NSOperationQueues和异步行为的问题,因此我将发布我的解决方案来回答这个问题


请注意,我只回答我自己的问题,因为我检查了meta.stackoverflow,他们说这是可以接受和鼓励的,我希望下面的答案可以帮助人们理解为什么他们的运行循环会锁定在NSOperations中,以及他们如何通过外部回调正确完成NSOperations。(其他线程上的回调)

问题的答案#1

我有一个NSOperation,它在其主方法中调用异步操作,在操作外部调用,我需要告诉该操作它已完成并结束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]
因为您的主线程不会更新,但您还有几个其他操作 必须在该操作完成之前执行,并且所有操作都不应阻止主线程

进入

dispatch_semaphore_t
如果需要从主线程启动多个相关的NSO操作,则可以通过 向NSOperation发送一个调度信号量,请记住,这些是NSOperation main方法内部的异步调用,因此NSOperation子类需要等待这些回调完成。 此外,从回调链接方法可能是一个问题

通过从主线程传入信号量,您可以使用[NSOperation addOperations:…waitUntilFinished:NO],并且在回调全部完成之前,仍然可以阻止执行其他操作

创建NSO操作的主线程的代码

//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];
//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;

}
…操作的代码

//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];
//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;

}
当另一个线程上的回调方法调用NSO操作上的setComplete时 三件事会发生

  • runloop将停止,以允许NSO操作完成(否则不会完成)

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

  • 操作将完成并解除分配

  • 如果使用方法2,则可以等待从NSOperationQueue调用的任意异步方法, 知道他们将完成runloop,您可以以任何方式链接回调,
    但我从不阻塞主线程。

    我没有详细阅读这些答案,因为这些方法a)过于复杂,b)没有按照设计的方式使用NSOperation。你们似乎在攻击已经存在的功能

    解决方案是将NSOperation子类化,并重写getter isConcurrent以返回YES。然后实现-(void)start方法并开始异步任务。然后,您负责完成任务,这意味着您必须在isFinished和isExecuting上生成KVO通知,以便NSOperationQueue可以知道任务已完成

    (更新:下面是如何将NSOperation子类化) (更新2:添加了在后台线程上工作时,如果有需要NSRunLoop的代码,如何处理NSRunLoop。例如Dropbox核心API)


    我不确定您为什么只想为运行循环使用NSOperation的所有开销,但我想如果您使用的是操作队列设计,那么它可能会很有用。我这样说的原因通常是,您只需在后台执行选择器,然后从那里调用CFRunLoopRun

    除此之外,下面是一个使用运行循环的NSOperation子类示例。只需将其子类化并重写willRun并调用需要运行循环才能工作的方法。一旦调用的所有方法都完成了,那么所有的运行循环源都被处理了,操作将自动结束。您可以在延迟后放置一个简单的执行选择器来测试它
    #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, e.g. 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
    
    TestLoop* t = [[TestLoop alloc] init];
    [t start];