objective-c - 如何在 NSOperation 中实现 NSRunLoop

标签 objective-c ios objective-c-blocks objective-c-runtime

我发布这个问题是因为我看到很多关于这个主题的困惑,结果我花了几个小时调试 NSOperation 子类。

问题是,当您执行异步方法时,NSOperation 对您没有多大好处,这些方法在异步回调完成之前实际上没有完成

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

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

您不能阻塞主线程,否则 UI 将被锁定,因此您有两个选择。

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

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

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

所以...您使用类似于以下内容的 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) 这个方法的第二个问题是,在 runLoops 必须正确终止的情况下(或者实际上完全从外部回调中的方法调用)

让我们假设当我调用它时我在主线程中有第二个,除非我希望 UI 锁定一点,并且不绘制任何东西,我不能在 NSOperationQueue addOperation 方法上说“waitUntilFinished:YES”.. .

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

由于关于 Cocoa 中的 runLoops、NSOperationQueues 和 Asynch 行为的问题太多,我将发布我的解决方案作为这个问题的答案。

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

最佳答案

问题 #1 的答案

我有一个 NSOperation,它在其 main 方法中调用一个异步操作,该方法在操作外部回调,我需要告诉该操作已完成并结束 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]

因为你的主线程不会更新,但是你还有几个其他的操作 在此 NSOperation 完成之前不得执行,并且它们中的任何一个都不应阻塞主线程。

输入...

dispatch_semaphore_t

如果你有几个依赖的 NSOperations 需要从主线程启动,你可以通过 向 NSOperation 发送信号量,记住这些是 NSOperation 主方法内部的异步调用,因此 NSOperation 子类需要等待这些回调完成。 来自回调的方法链接也可能是一个问题。

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

创建 NSOperation 的主线程代码

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

}

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

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

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

  3. NSOperation 将完成并被释放

如果你使用方法 2,你可以等待从 NSOperationQueue 调用的任意异步方法, 知道它们将完成 runloop,你可以用任何你喜欢的方式链接回调, 同时从不阻塞主线程。

关于objective-c - 如何在 NSOperation 中实现 NSRunLoop,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12285153/

相关文章:

ios - 用嵌套 block 保留 self ?

iphone - 取消引用空指针,警告

iphone - 从 iPhone 设置更改距离单位

ios - 无法从 iOS 应用程序打开 sqlite 数据库

ios - 隐藏导航栏上的后退按钮是iOS6

iPhone 应用程序崩溃并出现错误 [UIApplication _cachedSystemAnimationFenceCreatingIfNecessary :]

objective-c - UIView 不自动调整 subview

javascript - 长按 React Native 持续增加计数器

ios - 谁的 View 不在 iOS 6.1 的窗口层次结构中

objective-c - Objective-C block 是捕获属性值还是对象值?