RunLoop

RunLoop 的概念

一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:

function loop() { initialize(); do { var message = get_next_message(); process_message(message); } while (message != quit); }

OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。 CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。 NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。

RunLoop 与线程的关系

苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。 线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。

RunLoop 对外的接口

一个 RunLoop 包含若干个 Mode,每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

CFRunLoopSourceRef 是事件产生的地方。 CFRunLoopTimerRef 是基于时间的触发器。 CFRunLoopObserverRef 是观察者,当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
    kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
    kCFRunLoopExit          = (1UL << 7), // 即将退出Loop
}; 

RunLoop 的内部逻辑

RunLoop_1.png

总结下来就是:RunLoop 其内部是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。

苹果用 RunLoop 实现的功能

App 启动后 RunLoop 的状态,系统默认注册了5个Mode:

    1. kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
    1. UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
    1. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
  • 4: GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
  • 5: kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
AutoreleasePool

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。

  • 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。优先级最高,保证创建释放池发生在其他所有回调之前。
  • 第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。优先级最低,保证其释放池子发生在其他所有回调之后。
  • 在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

ASP.NET MVC是如何运行的[1]: 建立在“伪”MVC框架上的Web应用

ASP.NET由于采用了管道式设计,具有很好的扩展性,而整个ASP.NET MVC应用框架就是通过扩展ASP.NET实现的。通过上面对ASP.NET管道设计的介...

1825
来自专栏林德熙的博客

WPF 开发自动开机启动程序

将快捷方式创建在启动文件夹就可以让程序开机自动启动,上面的方法已经可以传入 lnkFilePath 所以只需要设置 lnkFilePath 是启动文件夹就可以

1062
来自专栏Oracle

Oracle内存数据库使用

t.inmemory, t.inmemory_priority, t.inmemory_distribute, t.inmemory_compression, ...

1102
来自专栏张善友的专栏

Visual Studio 2015 CTP6 发布

微软发布ASP.NET 5 支持在Windows、Mac和Linux上构建程序,Visual Studio 2015 CTP6(社区预览版)现已发布了。感兴趣的...

1878
来自专栏张善友的专栏

SimpleMembershipProvider 和 OAuth

ASP.NET MVC 4带了一个新的Membership 系统,Jon Galloway 写了一篇很详细的博客《SimpleMembership, Membe...

18710
来自专栏大内老A

通过几个Hello World感受.NET Core全新的开发体验

2016年6月27日,这是一个特殊的日子,微软全新的.NET开发平台.NET Core的RTM版本正式发布。我个人将.NET Core的核心特性归结为三点,它们...

2006
来自专栏Jackson0714

疯狂的ASP.NET系列-第一篇:啥是ASP.NET后续

2958
来自专栏葡萄城控件技术团队

ASP.NET MVC 5 - 查询Details和Delete方法

在这部分教程中,接下来我们将讨论自动生成的Details和Delete方法。 查询Details和Delete方法 打开Movie控制器并查看Details方法...

1956
来自专栏ASP.NETCore

Debugging into .NET Core源代码的两种方式

   .NET开源时间还不长,因为一直在做YOYOFx的关系,所似我常常有更深入的了解.NET Core和ASP.NET Core内容的需求,并且.NET Co...

1203
来自专栏進无尽的文章

NSRunLoop-iOS中隐藏的大杀器

NSRunLoop和CFRunLoopRef都代表着RunLoop对象.NSRunLoop是基于CFRunLoopRef的一层OC包装.

631

扫码关注云+社区