RunLoop是什么
runloop是什么?说白一点,他就是一个循环,当然我们代码while也可以让系统循环,但是他会导致CPU进入忙等状态,如果在主线程,还会卡死。而runloop就比较高级一点,他是一种“闲”等待,没事的时候会进入休眠,当有事件触发的时候,runloop会从休眠唤醒,执行任务。
源码分析
runloop的源码在这里下载
- 首先要理解一个概念,线程与runloop的关系,线程和runloop是一一对应的关系,甚至,它们在苹果的底层是用字典的key-value的形式来存储的,有朋友可能不信,我们来看看源码。 我们先创建一个线程,线程里面创建一个runloop:
thread = MyThread(target: self, selector: #selector(doSomething), object: nil)
thread?.start()
@objc func doSomething() {
print("start")
let runloop = RunLoop.current
print(runloop)
runloop.run()
print("end")
}
我们打开源码工程,找到Runloop.swift
我们可以找到
current的属性,他是调用底层的_CFRunLoopGet2方法的,我们定位进去发现只能看到这个方法的定义,而看不到实现,怎么办呢?
这个时候我们就可以看OC的源码了,swift没有,我们就参考下OC的。
我们打开CFRunLoop.c文件,可以看到CFRunloopGetCurrent实际上就是调用CFRunloopGet0方法。
可以看到
CFRunloopGet0的方法,他是如果没有当前runloop,先创建一个runloop,然后放到CFDictionary里面,然后再取出来。
这就证明了,runloop和线程是一一对应的关系,并且,他们是用字典来存起来的。
- 接下来,我们运行一下刚刚的代码,一看运行,发现,runloop并没有起到了线程阻塞循环的作用,依然会打印
end,这是为什么呢?
这就涉及到我们runloop的组成了。runloop由以下几点组成:
- Modes
查看源码我们发现,runloop苹果公开的只有2种模式,
default和common
- sources0:非基于内核事件手动触发的,比如用户触摸等。
- sources1:基于machport的内核事件,主动唤醒触发的。
- observers:用于监听runloop的状态的监听者。
- timers:定时任务。
很明显,我们上面的运行打印可以看到,如果只创建一个current的runloop,它的sources0、sources1、timers等都是nil,而结果就是,runloop没有起到阻塞线程的效果,所以我们就可以猜想,是不是sources和timer,至少一个要有呢?不然它怎么去处理事件?
- runloop增加sources有2个方法:
我们增加一个port试试:
@objc func doSomething() {
print("start")
let runloop = RunLoop.current
runloop.add(NSMachPort(), forMode: .common)
print(runloop)
runloop.run()
print("end")
}
我们发现,end没有打印了,并且,sources有了
这就印证了我们刚刚的想法,要想runloop起效果,还是得怎家sources,刚刚的add,还有timer的方法,有兴趣的自己可以试试。
- 为了验证我们的线程在runloop的加持下,是否一直保活,我们添加一个方法,这个方法就是在我们创建的线程里面执行多次任务,看看是否一直是那个线程执行。
@IBAction func performTask(_ sender: Any) {
if let thread = thread {
perform(#selector(addSubThreadAction), on: thread, with: nil, waitUntilDone: false)
}
}
@objc func addSubThreadAction() {
print("启动runloop后,\(String(describing: RunLoop.current.currentMode))")
print("---------子线程任务开始\(String(describing: Thread.current))")
for i in 0...3 {
Thread.sleep(forTimeInterval: 1.0)
print("-------子线程任务\(i)")
}
print("\(String(describing: Thread.current))----------子线程任务结束")
}
运行发现,我们可以多次地启动任务,并且都是在同一个线程里面执行,这就证明,该线程在runloop的加持下,一直活着。
- 如果我们想停止runloop怎么做呢?
CFRunLoopStop(CFRunLoopGetCurrent())
这个代码能帮到我们,我们试试?
结果发现,
end没有打印,这是为什么呢?
翻查文档我们发现:原来runloop的run方法,启动之后,就无法终止。
而runloop的启动其实有好几种方式:
我们翻看下源码发现其实三个方式,最终调用都是
public func run(mode: RunLoop.Mode, before limitDate: Date) -> Bool
再结合文档的提示,我们把代码改一下:
@objc func addSubThreadAction() {
print("启动runloop后,\(String(describing: RunLoop.current.currentMode))")
print("---------子线程任务开始\(String(describing: Thread.current))")
for i in 0...3 {
Thread.sleep(forTimeInterval: 1.0)
print("-------子线程任务\(i)")
}
print("\(String(describing: Thread.current))----------子线程任务结束")
isStop = true
CFRunLoopStop(CFRunLoopGetCurrent())
thread = nil
}
@objc func doSomething() {
print("start")
let runloop = RunLoop.current
runloop.add(NSMachPort(), forMode: .common)
print(runloop)
while !isStop {
runloop.run(mode: .default, before: Date.distantFuture)
}
print("end")
}
运行后发现,end打印了。
未完待续.......