了解NSRunLoop


108

谁能解释什么NSRunLoop?据我所知NSRunLoop,这与NSThread权利有关吗?所以假设我创建一个像

NSThread* th=[[NSThread alloc] initWithTarget:self selector:@selector(someMethod) object:nil];
[th start];

-(void) someMethod
{
    NSLog(@"operation");
}

所以在这个主题完成工作后,对吗?为什么使用RunLoops或在哪里使用?从苹果文档中我读了一些东西,但对我来说还不清楚,所以请尽可能简单地解释一下


这个问题范围太广。请对您的问题进行更具体的说明。
乔迪·哈金斯

3
起初,我想知道通用NSRunLoop的功能以及它与Thread的关系
taffarel 2012年

Answers:


211

运行循环是一种抽象,它(除其他外)提供一种机制来处理系统输入源(套接字,端口,文件,键盘,鼠标,计时器等)。

每个NSThread都有其自己的运行循环,可以通过currentRunLoop方法进行访问。

通常,您不需要直接访问运行循环,尽管有一些(网络)组件可以允许您指定它们将用于I / O处理的运行循环。

给定线程的运行循环将等待,直到其一个或多个输入源具有某些数据或事件,然后触发适当的输入处理程序来处理每个“就绪”的输入源。

这样做之后,它将返回其循环,处理来自各种来源的输入,如果没有工作要做则“睡眠”。

这是一个相当高级的描述(试图避免过多的细节)。

编辑

尝试解决评论。我把它弄碎了。

  • 这意味着我只能在线程内部访问/运行循环?

确实。NSRunLoop不是线程安全的,只能从运行循环的线程的上下文中进行访问。

  • 有没有简单的示例如何添加事件以运行循环?

如果要监视端口,则只需将该端口添加到运行循环中,然后运行循环将监视该端口的活动。

- (void)addPort:(NSPort *)aPort forMode:(NSString *)mode

您还可以使用以下方式显式添加计时器

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
  • 这意味着它将返回其循环吗?

运行循环将在每次迭代时(根据其模式)处理所有就绪事件。您将需要查看文档以发现有关运行模式的信息,因为这超出了常规答案的范围。

  • 启动线程时运行循环处于非活动状态?

在大多数应用程序中,主运行循环将自动运行。但是,您有责任启动运行循环并响应所旋转线程的传入事件。

  • 是否可以在线程外的线程运行循环中添加一些事件?

我不确定你在这里是什么意思。您不将事件添加到运行循环中。您添加输入源和计时器源(从拥有运行循环的线程中)。然后,运行循环监视它们的活动。当然,您可以提供来自其他线程和进程的数据输入,但是输入将由监视正在运行循环的线程上的那些源的运行循环处理。

  • 这是否意味着有时候我可以使用运行循环来阻塞线程一段时间

确实。实际上,运行循环将“停留”在事件处理程序中,直到该事件处理程序返回为止。您可以在任何应用程序中简单地看到它。为任何休眠的IO操作(例如,按下按钮)安装处理程序。您将阻塞主运行循环(和整个UI),直到该方法完成。

这同样适用于任何运行循环。

我建议您阅读有关运行循环的以下文档:

https://developer.apple.com/documentation/foundation/nsrunloop

以及如何在线程中使用它们:

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1


2
这意味着我只能在线程内部访问/运行循环?有没有简单的示例如何添加事件以运行循环?这意味着它将返回其循环吗?启动线程时运行循环处于非活动状态?是否可以在线程外的线程运行循环中添加一些事件?这是否意味着有时候我可以使用运行循环来阻塞线程一段时间?
taffarel 2012年

“为休眠的任何IO操作(例如,按下按钮)安装处理程序。” 你的意思是如果我继续按住手指,它会继续阻塞线程一段时间吗?
亲爱的

否。我的意思是,直到处理程序完成后,runloop才会处理新事件。如果您在处理程序中睡觉(或执行一些需要很长时间的操作),则运行循环将阻塞,直到处理程序完成其工作为止。
乔迪·哈金斯

@taffarel 是否可以在线程外的线程运行循环中添加一些事件?如果那意味着“我可以让代码随意在另一个线程的运行循环上运行”,那么答案确实是肯定的。只需调用performSelector:onThread:withObject:waitUntilDone:,传递一个NSThread对象,您的选择器就会安排在该线程的运行循环中。
Mecki

12

运行循环 交互式应用程序与命令行工具区分开来 。

  • 使用参数启动命令行工具,执行命令,然后退出。
  • 交互式应用程序等待用户输入,做出反应,然后继续等待。

这里

它们使您可以等到用户点击并做出相应的响应,再等到获得completeHandler并应用其结果,再等到获得计时器并执行功能。如果没有运行循环,则无法监听/等待用户敲击,也无法等待网络通话发生,除非您使用DispatchSourceTimer或,否则无法在x分钟之内唤醒DispatchWorkItem

也从这个评论

后台线程没有自己的运行循环,但是您可以只添加一个。例如,AFNetworking 2.x做到了。对于后台线程上的NSURLConnection或NSTimer,这是一种经过实践检验的真正技术,但是由于更新的API消除了这样做的必要,因此我们不再自己这样做。但是似乎URLSession确实做到了,例如,这是简单的request,正在主队列上运行[请参见图像的左面板]完成处理程序,并且您可以看到它在后台线程上具有运行循环


特别是关于:“背景线程没有自己的运行循环”。以下计时器无法触发异步调度:

class T {
    var timer: Timer?

    func fireWithoutAnyQueue() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { _ in
            print("without any queue") // success. It's being ran on main thread, since playgrounds begin running from main thread
        })
    }

    func fireFromQueueAsnyc() {
        let queue = DispatchQueue(label: "whatever")
        queue.async {
            self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from a queue — async") // failed to print
            })
        }
    }

    func fireFromQueueSnyc() {
        let queue = DispatchQueue(label: "whatever")
        queue.sync {
            timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from a queue — sync") // success. Weird. Read my possible explanation below
            })
        }
    }

    func fireFromMain() {
        DispatchQueue.main.async {
            self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from main queue — sync") //success
            })
        }
    }
}

我认为该sync块也运行的原因是:

同步块通常仅在其队列中执行。在此示例中,源队列是主队列,任何队列都是目标队列。

为了测试我RunLoop.current在每个调度中都登录了。

同步调度的运行循环与主队列相同。异步块中的RunLoop与其他实例不同。您可能在想为什么RunLoop.current返回不同的值。这不是共享的价值吗?好问题!进一步阅读:

重要的提示:

类属性 current不是一个全局变量。

返回当前线程的运行循环。

它是上下文相关的。仅在线程范围内(即线程本地存储)可见。有关更多信息,请参见此处

这是计时器的已知问题。如果使用,您不会遇到相同的问题DispatchSourceTimer


8

RunLoops有点像一个盒子,里面发生了一切。

基本上,在RunLoop中,您要处理一些事件,然后返回。如果超时之前未处理任何事件,则返回。您可以说它类似于异步NSURLConnections,它在后台处理数据而不干扰您的当前循环,但同时您需要同步处理数据。可以在RunLoop的帮助下完成,RunLoop可以使您异步NSURLConnection并在调用时提供数据。您可以像这样使用RunLoop:

NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];

while (YourBoolFlag && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil]) {
    loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
}

在此RunLoop中,它将运行直到完成其他一些工作并将YourBoolFlag设置为false 为止

同样,您可以在线程中使用它们。

希望对您有帮助。


0

运行循环是与线程关联的基本基础结构的一部分。运行循环是事件处理循环,可用于安排工作并协调收到的事件的接收。运行循环的目的是在有工作要做时让线程忙,而在没有工作时让线程进入睡眠状态。

从这里


CFRunLoop的最重要功能是CFRunLoopModes。CFRunLoop与“运行循环源”系统一起使用。源在一种或几种模式的运行循环中注册,并且使运行循环本身以给定模式运行。当事件到达源时,如果源模式与运行循环当前模式匹配,则仅由运行循环处理该事件。

从这里

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.