ios 了解NSRunLoop

zf2sa74q  于 2023-01-27  发布在  iOS
关注(0)|答案(6)|浏览(98)

有人能解释一下NSRunLoop是什么吗?我知道NSRunLoop是与NSThread相关的东西,对吗?所以假设我创建了一个线程

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

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

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

g0czyy6m

g0czyy6m1#

运行循环是一种抽象概念,它提供了一种处理系统输入源(套接字、端口、文件、键盘、鼠标、计时器等)的机制。
每个NSThread都有自己的运行循环,可以通过currentRunLoop方法访问该循环。
一般来说,您不需要直接访问运行循环,尽管有一些(网络)组件可以允许您指定它们将使用哪个运行循环来进行I/O处理。
给定线程的运行循环将等待,直到它的一个或多个输入源具有一些数据或事件,然后激发适当的输入处理程序来处理每个"就绪"的输入源。
在这样做之后,它将返回到它的循环,处理来自各种源的输入,如果没有工作要做,则"休眠"。
这是一个相当高层次的描述(试图避免太多的细节)。

    • 编辑**

我试图回应这条评论。我把它分成了几部分。

  • 这意味着我只能在线程内部访问/run to run循环,对吗?

事实上,. NSRunLoop不是线程安全的,只能从运行该循环的线程的上下文访问。

  • 有什么简单例子可以告诉你如何在运行循环中添加事件吗?

如果您想监控某个端口,只需将该端口添加到run循环中,然后run循环将监视该端口的活动。

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

也可以使用显式添加计时器

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
  • 什么意思是它会回到它的循环?

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

  • 当我启动线程时,运行循环是不活动的吗?

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

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

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

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

事实上,一个run循环会"停留"在一个事件处理程序中,直到该事件处理程序返回。你可以在任何应用程序中看到这一点。为任何休眠的IO操作(例如,按钮按下)安装一个处理程序。你将阻塞主run循环(和整个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

wkftcu5l

wkftcu5l2#

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

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

here开始
它们允许你等待用户点击并做出相应的响应,等待你得到一个completionHandler并应用它的结果,等待你得到一个计时器并执行一个函数。如果你没有一个runloop,你就不能监听/等待用户点击,你不能等待一个网络呼叫发生,你不能在x分钟内被唤醒,除非你使用DispatchSourceTimerDispatchWorkItem
也来自这条评论:
后台线程没有自己的runloop,但是你可以添加一个。例如AFNetworking 2.x就做了。这是NSURLConnection或NSTimer在后台线程上使用过的可靠技术,但是我们自己不再这么做了,因为更新的API消除了这样做的需要。但是看起来URLSession做了,例如here is simple request,在主队列上运行[见图片左侧面板]完成处理程序,您可以看到它在后台线程上有一个run循环
具体涉及:"后台线程没有自己的runloops"。以下计时器无法为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 * 队列中执行。在这个例子中,source队列是main队列,* whatever * 队列是目标队列。

为了测试,我在每个派遣中都记录了RunLoop.current
同步调度具有与主队列相同的runloop。而异步块中的RunLoop是与其他示例不同的示例。您可能会想为什么RunLoop.current返回不同的值。它不是 * shared * 值吗!?好问题!请进一步阅读:

    • 重要说明:**
    • 类属性**current不是全局变量。

返回当前线程的运行循环。
它是上下文相关的。它只在线程即Thread-local storage的作用域中可见。更多信息请参见here
这是定时器的已知问题。如果使用DispatchSourceTimer,则不会出现相同的问题

qyswt5oh

qyswt5oh3#

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

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

希望这对你有帮助。

nkoocmlb

nkoocmlb4#

运行循环是与线程关联的基本基础结构的一部分。运行循环是一种事件处理循环,用于调度工作和协调传入事件的接收。运行循环的目的是在有工作要做时使线程保持忙碌状态,在没有工作要做时使线程休眠。
从这里
CFRunLoop最重要的特性是CFRunLoopModes。CFRunLoop与"运行循环源"系统一起工作。源在运行循环上注册为一个或多个模式,并且运行循环本身在给定模式下运行。当事件到达源时,如果源模式与运行循环当前模式匹配,则仅由运行循环处理。
从这里

jqjz2hbq

jqjz2hbq5#

Swift
let runLoop = RunLoop.current

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

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

r55awzrz

r55awzrz6#

    • iOS运行循环**

RunLoop(EventLoop, Looper)EventLoop(事件处理循环)模式的实现。它基于NSRunLoopCFRunLoopRef的 Package 器)
Official doc
运行循环是与线程关联的基本基础结构的一部分。运行循环是一种事件处理循环,用于调度工作和协调传入事件的接收。运行循环的目的是在有工作要做时使线程保持忙碌状态,在没有工作要做时使线程休眠。
单个线程可以在单个模式中具有单个RunLoop。只有具有此模式的事件才会被处理,所有其他事件都将等待RunLoop在该模式下启动
RunLoop是一种机制(基于循环(for,while)),它将计划任务(例如回调队列)移动到线程(线程堆栈)。RunLoop在线程堆栈为空时工作(event processing loop)。
event processing loop是在.entry.exit之间运行循环的时间。在此期间,RunLoop以特定模式处理所有计划任务。所有其他模式及其自己的队列将在
默认情况下,应用程序具有main threadRunLoop(main loop)。在其他情况下,应手动创建
main run loop负责清空应用程序中的main queue

//Run loop for the current thread
RunLoop.current

//Run loop of the main thread.
RunLoop.main

    • 模式**

运行循环模式是要监视的输入源和计时器的集合以及要通知的运行循环观察器的集合。
模式:

  • default-默认情况下使用
  • 跟踪-例如当您滚动UITableView scrollViewDidScroll
  • common(是类似[default,tracking]的伪模式)
  • <custom>-您可以创建自己的模式
//current input mode
RunLoop.current.currentMode

例如:

  • UIView.draw(_ rect:),按钮操作...使用default mode
  • NSObject.perform(_:with:afterDelay:)使用default mode
  • DispatchQueue.main.async使用common mode
  • Timer.scheduledTimer使用default mode。这就是为什么当UI滚动发生时(在跟踪模式下),计时器不会触发(在默认模式下)。要修复此问题,请使用通用模式-RunLoop.main.add(timer, forMode: .common)
  • 组合RunLoop.mainDispatchQueue.main.receive(on:, options:))。RunLoop. main使用RunLoop.perform(_:),后者使用default mode,DispatchQueue. main使用DispatchQueue.main.async,后者使用common mode
    • 输入源和定时器**

运行循环接收事件:

  • Input sources-异步事件(触发时)消息
  • 基于端口-来自另一个线程或进程。由内核自动发出信号
  • 自定义输入源-用户启动的事件-用户操作、网络事件。必须从另一个线程手动发出信号
  • performSelector: onThread
  • Timer sources-同步事件(在特定时间)计时器

它们可以添加到多种模式

    • 观察员**

监视RunLoop的状态更改

    • 创建运行循环**

创建新线程,设置RunLoop并启动线程
1.创建运行循环运行循环. current
1.运行循环必须至少有一个输入源或计时器才能监视RunLoop。add(_timer:定时器,用于Mode模式:运行循环.模式)运行循环.添加(_a端口:端口,用于Mode模式:运行循环模式)
1.运行运行循环www.example.com() RunLoop.run ()

let thread = Thread {
    //1. create RunLoop
    //create a new one or return existing run loop for current thread
    //use RunLoop.current instead of RunLoop()
    let customRunLoop = RunLoop.current
    
    //add observer for current RunLoop for cpecufic mode
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), customObserver, CFRunLoopMode.commonModes)

    //2. A run loop must have at least one input source or timer to monitor
    let timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (timer) in
        //.default mode
    }
    customRunLoop.add(timer, forMode: .default)

    //3. run RunLoop
    //If no input sources or timers are attached to the run loop, this method exits immediately
    //infinite loop that processes data from the run loop’s input sources and timers.
    //calls RunLoop.run(mode:.default before:)
    customRunLoop.run()
    
    //------
    
    //create custom mode
    let customRunLoopMode = RunLoop.Mode("customeMode")
    
    //2. A run loop must have at least one input source or timer to monitor
    //Will be called when previous RunLoop.run() is done(no input sources or timers) - exit from loop
    let timer2 = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (timer) in
        //"customeMode" mode
    }
    customRunLoop.add(timer2, forMode: customRunLoopMode)
    
    //3. run RunLoop
    let isInputSourcesOrTimers = customRunLoop.run(mode: customRunLoopMode, before: Date.distantFuture)
}

thread.start()

let customObserver = CFRunLoopObserverCreateWithHandler(nil, CFRunLoopActivity.allActivities.rawValue , true, 0) { observer, activity in
    switch (activity) {
    case .entry:
        break
    case .beforeTimers:
        break
    case .beforeSources:
        break
    case .beforeWaiting:
        break
    case .afterWaiting:
        break
    case .exit:
        break
    case .allActivities:
        break
    default:
        break
    }
}

相关问题