RunLoop为了实现程序不退出,在没有事件处理时休眠,在有事件到来时立刻被唤醒。
充分节省CPU资源,提高程序性能。
我们不能创建/显示管理RunLoop对象,系统会在需要的时候为每个线程创建一个RunLoop对象。
1、概念
管理来自window system、Port objects、NSConnection objects、Timer的事件的对象,如:鼠标和键盘的事件。(NSTimer不是“Input”,触发时不会导致run循环返回)
2、Modes
RunLoop会在不同的时候被设置为不同的Mode
1)common:modes的组合,可以将source、timers、observers注册到这个set中,他们将在这些mode中共享
2)Default:处理非NSConnection对象的输入源模式(通常主线程是在这个Mode下运行)
3)eventTracking:当以模式跟踪事件时(例如拖拽、滑动事件)
4)modalPanel:当等待模式面板的输入时(如NSSavePanel或NSOpenPanel)
5)tracking:在跟踪控制时
3、item事件
RunLoop --> n个Modes --> n个<Source/Timer/Observer>
切换Mode需要退出Loop,设置Mode后再进入。为了将不同组的<Source/Timer/Observer>分隔开,让其互不影响。
4、结构
代码语言:javascript复制typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // 一个回调,需要sourceSignal、wakeUp
CFMutableSetRef _sources1; // 一个port,一个回调,内核和其他线程交互
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
1、Source:
1)Source0:触摸事件,PerformSelectors。
一个回调,不能主动触发事件。
需要先调用CFRunLoopSourceSignal(source)标记为待处理,
然后调用CFRunLoopWakeUp(runloop)来唤醒RunLoop处理这个事件
2)Source1:基于Port的线程间通信。
一个mach_port、一个回调,用于通过内核和其他线程互相发送消息。能主动唤醒RunLoop
2、Timer:
1个时间、1个回调。加入RunLoop时会注册对应的时间点,到时会被唤醒处理回调
3、Observer:
观察者,包含一个回调。当RunLoop状态发生变化时,会触发回调。可以观察的时间点有:
1)Entry:即将进入Loop
2)BeforeTimer:即将处理Timer
3)BeforeSource:即将处理Source
4)BeforeWaiting:即将进入休眠
5)AfterWaiting:刚从休眠中唤醒
6)Exit:即将退出Loop
如果一个Mode中以上任何一个item都没有,就会退出,不进入循环。
CommonModes:
1个Mode可以将自己标记为Common属性,会将起ModeName添加在RunLoop的_commonModes中。每当RunLoop的内容发送变化时,会将_commonModeItems中的<Source/Observer/Timer>自动同步到具有Common标记的所有Mode里(即_commonModes集合里的)
- 应用举例:NSTimer
主线程中又两个预置的Model:Defaut和Tracking,被标记为Common属性。
1)将Timer分别加入这两个Mode
2)将Timer加入到顶层RunLoop的_commonModeItems中
5、苹果用RunLoop实现的功能
App启动后 ,系统默认
- 注册了5个Mode:
1)NS Default RunLoop Mode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
2)UI Tracking RunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
3)UI Initialization RunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
4)GS EventReceive RunLoopMode: 接受系统事件的内部 Mode,通常用不到。
5)NSRunLoopCommonModes : 这是一个占位的 Mode,没有实际作用。
- 注册了两个 Observer ( AutoreleasePool 相关操作)
回调Callout:都是 _wrapRunLoopWithAutoreleasePoolHandler()
第一个 Observer 监视的事件是
Entry (即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。
(其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。)
第二个 Observer 监听NSRunLoop运行状态:
BeforeWaiting (准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;
Exit (即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。
(其 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。)
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
6、底层架构
苹果官方将整个系统大致划分为上述4个层次:
应用层:包括用户能接触到的图形应用,例如 Spotlight、Aqua、SpringBoard 等。
应用框架层:即开发人员接触到的 Cocoa 等框架。
核心框架层:包括各种核心框架、OpenGL 等内容。
Darwin:即操作系统的核心,包括系统内核、驱动、Shell 等内容,这一层是开源的,其所有源码都可以在 opensource.apple.com 里找到。
7、使用
1)解决NSTimer在ScrollView滑动时不work
[runLoop addTimer :timer forMode : NSDefaultRunLoopCommonMode ]; // 将Timer注册到给定的Mode中
timer firing时:会调用RunLoop中关联的Object的selector
2)保持线程常驻
如:在子线程中的afterDelay不work(子线程不会自动创建RunLoop,导致Timer不工作)
在子线程的代码中:
NSRunLoop *runLoop = [ NSRunLoop currentRunLoop ]; // 不获取就不会主动创建
[runLoop addPort :[ NSMachPort port ] forMode : NSDefaultRunLoopMode ];
[runLoop run ];
参考文献:
深入理解RunLoop
iOS底层原理总结 - RunLoop