[iOS 理解] RunLoop

理解 runloop 步骤
1 下载源码
2 让 Makefile管理的 C/C++ 项目使用Xcode阅读时源码高亮、支持跳转
3 先学习这篇博客
建议第一天看一遍这篇文章,不求理解
第二天再结合源码看,同时追求理解
第三天只看没理解的地方,并且自己悟
这类东西最好不要一天学完
结束


自己总结核心逻辑

通知要进入循环
开始循环
	通知要执行 timer 回调
	通知要执行 source 回调
	
	如果有被 signal 的 source0,就执行 source0 的回调
	
	读取 port,timeout 为 0,看有没有收到消息,如果有,跳转到 *

	[ 如果前面执行了source0 回调,就不等了,即下面读 port,timeout 为0 ,应该进行下一个loop ]
	通知即将开始等待(可能不通知)
	
	开始等待 (msg_trap),直到 port 收到消息
	
	通知刚被唤醒
	
*	处理消息:
	如果 port 是 timerPort,执行 timer 回调:所有到期的 timer,然后安排最近的 timer
	如果 port 是 dispatchPort,执行 GCD 安排到主线程的任务
	否则说明是 source1 被 signal 了,执行。
	
	判断是否退出循环
	
通知循环退出

应用

1 AutoreleasePool
苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler,根据不同 activity 执行不同代码。
第一个 observer 优先级最高,只监听 runLoop 即将进入 loop 时,调用 objc_autoreleasePoolPush
第二个 observer 优先级最低,监听多个activity:BeforeWaiting 时调用 _objc_autoreleasePoolPop 和 objc_autoreleasePoolPush 释放旧池、创建新池,退出循环时 _objc_autoreleasePoolPop 释放池
主线程执行的代码通常是回调出去的,这些回调有 Runloop 创建的 AutoreleasePool 管理,所以不必显式创建。

2 UI 更新
操作 frame 或图层或 setNeeds 会标记对应的 UIView/CALayer,提交到一个全局容器。
苹果注册一个 Observer 监听 BeforeWaiting 和 Exit Loop,遍历所有需要处理的对象,计算更新绘制

3 网络请求
使用 NSURLConnection 时,会传入一个 delegate,然后 [connection start]
这时,当前线程会获取 runloop,注册四个 source0
其中一个 source0 的回调是用来调用 delegate 方法,一个用来处理 cookie storage,等等
然后创建两个新线程:
一个是 NSURLConnectionLoader 线程,其 runloop 注册了一个 source1,回调内处理 socket 传来的数据,然后触发 source0
一个是 CFSocket,处理底层 socket 连接,触发 source1
AFNetworking 设置一个后台常驻服务线程,为了不让线程退出,让 runloop 监听某个port。

4 界面流畅框架 AsyncDisplayKit
页面卡顿原因:
1 计算:排版需要大量计算:如计算视图大小、文本高度等
2 绘制:CoreText 复杂绘制、图片绘制 (例如预先解压)、元素绘制 (Quartz)等
3 UI对象操作:UIView/CALayer 等 UI 对象的更新、创建、销毁
前两类操作可以通过各种方法扔到后台线程执行,UI 更新必须在主线程执行
ASDK 尽量把能放入后台的任务放入后台,不能的尽量推迟
ASDK 创建了一个名为 ASDisplayNode 的对象,并在内部封装了 UIView/CALayer,它具有和 UIView/CALayer 相似的属性,例如 frame、backgroundColor等。所有这些属性都可以在后台线程更改,开发者可以只通过 Node 来操作其内部的 UIView/CALayer,这样就可以将排版和绘制放入了后台线程。但是无论怎么操作,这些属性总需要在某个时刻同步到主线程的 UIView/CALayer 去。
ASDK 仿照 QuartzCore/UIKit 框架的模式,实现了一套类似的界面更新的机制:即在主线程的 RunLoop 中添加一个 Observer,监听了 kCFRunLoopBeforeWaiting 和 kCFRunLoopExit 事件,在收到回调时,遍历所有之前放入队列的待处理的任务,然后一一执行。

有的 app 针对滑动时异步加载网络上的图片会卡一下,非常简陋的解决方法:让 setImage 在 Mode 为 default 时执行,所以会出现滑动结束时才刷图,交互很不好,但是是一个思路

—————
运行接口
NSRunloop
run / runUntilDate 都不能通过 CFRunLoopStop 来停止,runMode:beforeDate 可以
CFRunLoop能直接停止掉所有的CFRunLoop运行起来的runloop

其他 note
1 两大类事件源:Timer Source和Input Source(包括performSelector *方法簇、Port或者自注册的Input Source 自己 signal)
2 注册 Input Source 与 signal source
3 关于发送 source0 事件,因为 runloop 大概率在 msg_trap,所以要调用 CFRunnLoopWakeUp 唤醒
4 关于添加timer,timer 数组是按时间排序的,然后安排监听最近要发生的 timer。只需要监听最近的
timer 依靠 mk_timer 驱动
timer 回调后会根据重复间隔等属性决定释放或者调整后重新加入

最开始学习时的 note
一个RunLoop对象包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。
每次让 RunLoop 开跑时,只能指定一个 Mode。
如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 开始。

Runloop 有一个 commonModes 集合,加入 common mode 的item 会加入集合中的所有 mode
现在含 default 和 tracking 两个预置 mode

概念
RunLoop 实际上是一个对象,提供了一个入口函数来执行 Event Loop 的逻辑:
“接受消息->等待->处理” 循环,直到达到循环结束条件退出。

Runloop 不能手动创建,只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()
根据 CFRunLoopRef _CFRunLoopGet(pthread_t thread) 逻辑
可知:

  • RunLoop与线程一一对应,保存在字典中:pthread_t -> CFRunLoopRef
  • 主线程的RunLoop在初始化字典时创建(并加入字典)
  • 其他线程的 RunLoop在第一次获取时创建,在线程结束时通过回调销毁

五个类

  • CFRunLoopRef:RunLoop对象
  • CFRunLoopModeRef:RunLoop的运行模式
  • CFRunLoopSourceRef:事件产生的地方
  • CFRunLoopTimerRef:定时器 = NSTimer
  • CFRunLoopObserverRef:观察者,回调 runloop 状态
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值