了解 NSRunLoop

有人能解释一下什么是 NSRunLoop吗?所以我知道 NSRunLoop是和 NSThread连接的东西,对吗?假设我创建了一个类似

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


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

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

73299 次浏览

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

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

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

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

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

That's a pretty high level description (trying to avoid too many details).

剪辑

试图解决这个问题,我把它撕成了碎片。

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

Indeed. NSRunLoop is not thread safe, and should only be accessed from the context of the thread that is running the loop.

  • is there any simple example how to add event to run loop?

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

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

还可以显式添加计时器

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

运行循环将在每次迭代中处理所有就绪事件(根据其模式)。您将需要查看有关运行模式的文档,因为这有点超出了一般答案的范围。

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

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

  • is it possible to add some events to Thread run loop outside the thread?

我不明白你的意思。不向运行循环添加事件。您可以添加输入源和计时器源(来自拥有运行循环的线程)。然后 run 循环监视它们的活动。当然,您可以提供来自其他线程和进程的数据输入,但是输入将由运行循环上监视这些源的运行循环处理。

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

确实。事实上,运行循环将“停留”在事件处理程序中,直到该事件处理程序返回。你可以在任何应用程序中看到这一点。为任何处于睡眠状态的 IO 操作(例如,按钮)安装一个处理程序。您将阻塞主运行循环(以及整个 UI) ,直到该方法完成。

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

我建议您阅读以下关于 run 循环的文档:

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

RunLoops 有点像一个盒子,里面的事情就这么发生了。

基本上,在 RunLoop 中,您将处理一些事件,然后返回。或者,如果在超时命中之前没有处理任何事件,则返回。 You can say it as similar to asynchronous NSURLConnections, Processing data in the background without interfering your current loop and but at the same time, you require data synchronously. 这可以在 RunLoop 的帮助下完成,RunLoop 使您的异步 NSURLConnection在调用时提供数据。 你可以这样使用 RunLoop:

NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];


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

在这个 RunLoop 中,它将一直运行,直到您完成其他一些工作并将 你的 BoolFlag设置为 假的

类似地,您可以在线程中使用它们。

希望这个能帮到你。

运行循环是 分开 互动应用程序的来源 命令行 tools。

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

来自 给你

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

同样来自 this comment:

后台线程没有自己的 runloop,但是您可以直接添加 例如 AFNetworking 2. x就是这样做的。这是一个经过实践检验的技术 NSURLConnection or NSTimer on background threads, but we don't do this 因为新的 API 消除了这样做的必要性。但是 看起来 URLSession 正在运行,例如 我有个简单的要求(参见图像的左面板) 完成处理程序,您可以看到它有一个运行 后台线程上的循环


具体地说: “后台线程没有自己的 runloop”。下面的定时器无法触发 异步分派:

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 块通常只是从它们的 来源队列中执行。在本例中,源队列是主队列,随便啦队列是目标队列。

为了测试我在每次派遣中都记录了 RunLoop.current

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

重要提示:

类别财产 current不是全局变量。

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

It's contextual. It's visible only within the scope of the thread ie 线程本地存储. For more on that see 给你.

这是计时器的一个已知问题。如果使用 DispatchSourceTimer,就不会出现同样的问题

Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.

从这里


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

从这里

Swift
let runLoop = RunLoop.current


Obj-c
NSRunLoop * runloop = [NSRunLoop currentRunLoop];

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