iOS_RunLoop、Modes、Source、Timer、Observer、主要结构...

2022-07-20 14:12:19 浏览数 (3)

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

0 人点赞