【问题标题】:Understanding NSRunLoop了解 NSRunLoop
【发布时间】:2012-08-18 22:39:49
【问题描述】:

谁能解释一下NSRunLoop 是什么?所以据我所知NSRunLoop 是与NSThread 有关的东西,对吗?所以假设我创建了一个像

这样的线程
NSThread* th=[[NSThread alloc] initWithTarget:self selector:@selector(someMethod) object:nil];
[th start];

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

所以在这个线程完成他的工作之后,对吗?为什么使用RunLoops 或在哪里使用?从Apple docs我读过一些东西,但对我来说不清楚,所以请尽可能简单地解释一下

【问题讨论】:

标签: ios objective-c cocoa-touch nsrunloop


【解决方案1】:

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

每个 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

【讨论】:

  • 这意味着我只能访问/运行以在线程内运行循环,对吗?有没有简单的例子如何将事件添加到运行循环?什么意味着它将返回到它的循环?当我启动线程时,运行循环是否处于非活动状态?是否可以在线程外向线程运行循环添加一些事件?这是否意味着有时我可以使用运行循环来阻塞线程一段时间?
  • “为任何休眠的 IO 操作(例如,按下按钮)安装处理程序。”你的意思是如果我继续按住按钮,它会继续阻塞线程一段时间?!
  • 没有。我的意思是,在处理程序完成之前,runloop 不会处理新事件。如果你在处理程序中休眠(或执行一些需要很长时间的操作),那么运行循环将阻塞,直到处理程序完成其工作。
  • @taffarel 是否可以在线程外的线程运行循环中添加一些事件? 如果这意味着“我可以让代码随意在另一个线程的运行循环上运行”那么答案确实是肯定的。只需调用performSelector:onThread:withObject:waitUntilDone:,传递一个NSThread 对象,您的选择器就会被安排在该线程的运行循环中。
【解决方案2】:

RunLoops 有点像一个盒子,事情刚刚发生。

基本上,在一个 RunLoop 中,你去处理一些事件然后返回。或者如果在超时之前它没有处理任何事件,则返回。 你可以说它类似于异步 NSURLConnections,在后台处理数据而不干扰你当前的循环,但同时,你需要同步数据。 这可以在 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

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

希望对你有所帮助。

【讨论】:

    【解决方案3】:

    运行循环是 分隔 交互式应用的 命令行工具。

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

    来自here

    它们允许你等到用户点击并做出相应的响应,等到你得到一个completionHandler并应用它的结果,等到你得到一个计时器并执行一个功能。如果你没有运行循环,那么你不能监听/等待用户点击,你不能等到网络调用发生,你不能在 x 分钟内被唤醒,除非你使用 DispatchSourceTimer 或 @ 987654329@

    同样来自this comment

    后台线程没有自己的运行循环,但你可以添加 一。例如。 AFNetworking 2.x 做到了。这是经过尝试和真正的技术 NSURLConnection 或 NSTimer 在后台线程上,但我们不这样做 我们自己不再需要了,因为更新的 API 消除了这样做的需要。但 似乎 URLSession 确实,例如,here is simple request,正在运行 [参见图像的左侧面板] 主队列上的完成处理程序,您可以看到它已运行 在后台线程上循环


    特别是:“后台线程没有自己的运行循环”。 async 调度无法触发以下计时器:

    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 块也运行的原因是:

    sync 块通常只是从它们的 source 队列中执行。在此示例中,源队列是主队列,whatever 队列是目标队列。

    为了测试我在每次调度中都记录了RunLoop.current

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

    重要提示:

    类属性 current 不是全局变量。

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

    这是上下文相关的。它仅在线程范围内可见,即Thread-local storage。有关更多信息,请参阅here

    这是定时器的一个已知问题。如果您使用DispatchSourceTimer,则不会遇到同样的问题

    【讨论】:

      【解决方案4】:

      运行循环是与线程相关的基础架构的一部分。运行循环是一个事件处理循环,用于安排工作和协调接收传入事件。运行循环的目的是在有工作要做的时候让你的线程保持忙碌,而在没有工作的时候让你的线程进入睡眠状态。

      From here


      CFRunLoop 最重要的特性是 CFRunLoopModes。 CFRunLoop 与“运行循环源”系统一起工作。源在一个或多个模式的运行循环上注册,并且运行循环本身在给定模式下运行。当事件到达源时,只有源模式与运行循环当前模式匹配时,才会由运行循环处理。

      From here

      【讨论】:

        【解决方案5】:
        Swift
        let runLoop = RunLoop.current
        
        Obj-c
        NSRunLoop * runloop = [NSRunLoop currentRunLoop];
        

        运行循环是一个事件处理循环,用于持续监控和处理输入事件,并将它们分配给相应的目标进行处理。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多