RunLoop 的坑

由于项目中, 使用 runLoop.run()方法, 当执行 remove portCFRunLoopStop(CFRunLoopGetCurrent()), RunLoop一直没有退出, 造成开启RunLoop 之后的代码一直没有执行, 查阅了苹果 API 说明, 总结了下面的问题。

文中包含大量的 API 说明,如果只关心结果,请直接参考中文文字。

RunLoop.run()

If no input sources or timers are attached to the run loop, this method exits immediately;
otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invokingrun(mode:before:). In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.

Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. macOS can install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.

RunLoop.run(until:)

If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking run(mode:before:) until the specified expiration date.

Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. macOS can install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.

RunLoop.run(mode:before:)

If no input sources or timers are attached to the run loop, this method exits immediately and returns false; otherwise, it returns after either the first input source is processed or limitDate is reached. Manually removing all known input sources and timers from the run loop does not guarantee that the run loop will exit immediately. macOS may install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.

Manually removing all known input sources and timers from the run loop does not guarantee that the run loop will exit immediately.

swift RunLooprun() 方法,即使 remove 所有 sources, timers , 也不会立刻 exit,因为在其内部实现反复调用 run(mode:before:), 所以 run() 本身是用来开启一个无限循环的 Loop。

CFRunLoopRun()

The current thread’s run loop runs in the default mode (see Default Run Loop Mode) until the run loop is stopped with CFRunLoopStop(_:) or all the sources and timers are removed from the default run loop mode.

Run loops can be run recursively. You can call CFRunLoopRun() from within any run loop callout and create nested run loop activations on the current thread’s call stack.

CFRunLoopRunInMode(_ : _ : _ :)

The run loop exits with the following return values under the indicated conditions:

  • kCFRunLoopRunFinished. The run loop mode mode has no sources or timers.
  • kCFRunLoopRunStopped. The run loop was stopped with CFRunLoopStop(_:).
  • kCFRunLoopRunTimedOut. The time interval seconds passed.
  • kCFRunLoopRunHandledSource. A source was processed. This exit condition only applies when returnAfterSourceHandled is true.

You must not specify the commonModes constant for the mode parameter. Run loops always run in a specific mode. You specify the common modes only when configuring a run-loop observer and only in situations where you want that observer to run in more than one mode.

CFRunLoopStop(_:)

This function forces rl to stop running and return control to the function that called CFRunLoopRun() or CFRunLoopRunInMode(_:_:_:) for the current run loop activation. If the run loop is nested with a callout from one activation starting another activation running, only the innermost activation is exited.

看过 CFRunLoop 的介绍, 决定以后使用 CFRunLoop, 经过测试, 使用 CFRunLoopRun()CFRunLoopRunInMode(_ : _ : _ :) run 的 RunLoop , 可以使用CFRunLoopStop(_:)立即 退出。

下面简单说明一下 RunLoop 的实际应用:

  1. 可以使用 perform(_ aSelector: Selector, with anArgument: Any?, afterDelay delay: TimeInterval, inModes modes: [RunLoop.Mode]), 来延迟加载图片等, mode 设置为 default.
  2. 不退出的 thread , 在 threadRunLoop 中加入一个 port, 开启 RunLoop, 防止退出。
  3. 执行一系列任务之后,RunLoop 退出时, 处理问题。
    我的问题就发生在这里,当 remove port 和走 CFRunLoopStop(CFRunLoopGetCurrent()) 的时候, RunLoop 都没有退出。因为我用的是 RunLoop.current.run(),具体细节参考上面。

事例代码如下:

class ViewController: UIViewController {
    lazy var thread: Thread = {
        return Thread.init(target: self, selector: #selector(run), object: nil)
    }()
    let port = Port.init()
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        thread.start()
    }

    @objc func run() {
        print("run: ", Thread.current)
        RunLoop.current.add(port, forMode: .default)
        RunLoop.current.run()
        print("ran: ", Thread.current)
    }
    
    @objc func run1() {
        print("run1: ", Thread.current)
        CFRunLoopStop(CFRunLoopGetCurrent())
    }
    
    override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        perform(#selector(run1), on: thread, with: nil, waitUntilDone: false)
    }
}

RunLoop 具体介绍参考: 《深入理解RunLoop》

你可能感兴趣的:(RunLoop 的坑)