iOS总结-Runloop篇(三)-RunLoop的实现的功能

参考大神:https://blog.ibireme.com/2015/05/18/runloop/

AutoreleasePool

关于自动释放池,之前已经总结过了。它是和Runloop密切相关的。

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

第一个Observer监视的事件是Entry(即将进入Loop),其回调会调用_objc_autoreleasePoolPush()创建自动释放池,优先级最高,保证创建释放池发生在其他所有回调之前。

第二个Observer监视了两个事件:BeforeWaiting(准备进入休眠)时调用_objc_autoreleasePoolPop()和 _objc_autoreleasePoolPush()释放旧的池并创建新池;Exit(即将退出Loop)时调用_objc_autoreleasePoolPop()来释放自动释放池。这个优先级最低,保证其释放池子发生在其他所有回调之后。

在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被RunLoop创建好的AutoreleasePool环绕着,所以不会出现内存泄漏。

事件响应

苹果注册了一个Source1(基于 mach port 的)用来接收系统事件,其回调函数 为__IOHIDEEventSystemClientQueueCallback().

当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由IOKit.framework生成一个IOHIDEvent事件并由SpringBoard接收。SpringBoard只接收按键(锁屏、静音等),触摸,加锁,接近传感器等几种Event,随后用mach port 转发给需要的App进程。随后苹果注册的那个Source1就会触发回调,并调用_UIApplicationHandleEventQueue()进行应用内部的分发。

_UIApplicationHandleEventQueue() 会把IOHIDEvent 处理并包装成UIEvent进行处理或分发,其中包括识别UIGesture/处理屏幕旋转/发送给UIWindow等.通常如UIButton点击/touchesBegin/Move/End/Cancel事件都是在这个回调中完成的.

上面按钮点击中是__CFRunLoopDoSource1来触发硬件事件的回调,再有))CFRunLoopDoSource0来进行Event的分发,可以看到利用__dispatchPreprocessedEventFromEventQueue来分发->对应APP->UIWindow->UIControl...

手势识别

当_UIApplicationHandleEventQueue()是被一个手势时,首先会调用Cancel将当前touchesBegin/Move/End回调打断,然后系统将对应的UIGestureRecognizer标记为待处理,苹果注册一个 Observer 监测 BeforeWaiting(Loop即将进入休眠)事件, 这个Observer的回调函数是_UIGestureRecognizerUpdateObserver(), 其内部会获取所有刚被标记为待处理的,并执行GestureRecognizer回调.

界面更新

当在操作UI时,如改变了Frame/更新了UIView/CALayer 的层次时,或手动调用了UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理, 被提交到一个全局的容器去.

苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit(即将推出Loop) 事件, 回调去执行一个很长的函数:

_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面.

定时器

NSTimer其实就是CFRunLoopTimerRef, 他们之间是 toll-free bridged 的. 一个NSTimer 注册到 RunLoop 后, RunLoop会为其重复的时间点注册好事件. RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer, Timer有个属性叫做 Tolerance(宽容度), 标识了当时间点到后,容许有多少最大误差.

NSTimer是用来 XNU内核的 mk_timer, 不是 GCD驱动的.

CADisplayLink 是一个和屏幕刷新率一致的定时器 (但实际实现原理更复杂, 和NSTimer 并不一样,这个后面会总结.其内部实际是操作了一个 Source).如果在两次屏幕刷新之间执行了一个长任务, 那其中就会有一帧被跳过去 (和 NSTimer 相似),造成界面卡顿的感觉.在快速滑动TableView时,即使一帧的卡顿也会让用户有所察觉. FaceBook 开源的 AsyncDisplayLink 就是为了解决界面卡顿的问题,起内部用了 RunLoop.

PeformSelector

当调用 NSObject 的performSelector:afterDelay:/  performSelector:onTread:后, 实际上其内部会创建一个 Timer并添加到当前线程的RunLoop 中,如果当前线程没有RunLoop,这个方法会失效.

关于GCD

实际上 GCD提供的某些接口也用到了RunLoop,如 dispatch_async()

当调用 dispatch_async(dispatch_get_main_queue(),block) 时, libDispatch 会向主线程的RunLoop 发送消息, RunLoop会被唤醒, 并从消息中取得这个 block, 并在回调__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() 里执行这个 block.但是这个逻辑仅限于 dispatch 到主线程, dispatch 到其他线程仍然是由 libDispatch 处理的.

关于网络请求

CFSocket 是最底层的接口, 只负责 socket 通信

CFNetwork 是基于 CFSocket 等接口的上层封装 , NSURLConnection 是基于 CFNetwork 的更高层的封装, 提供面向对象的接口, AFNetworking工作于这一层.NSURLConnection 是基于 CFNetwork 的更高层的封装, 提供面向对象的接口, AFNetworking工作这一层.

NSURLSession 是iOS7 中新增的接口, 表面上是和NSURLConnection并列的, 但底层仍然用到了 NSURLConnection 的部分功能(如 com.apple.NSURLConnectionLoader 线程), AFNetworking2 和  Alamofire 工作于这一层.

虽然iOS网络已经放弃了NSURLConnection,改为 NSURLSession,但是我们还是有必要了解它的实现.

通常使用 NSURLConnection时, 你会传入一个 Delegate, 当调用了 [connection start]后, 这个 Delegate 就会不停收到事件回调.实际上, start 这个函数的内部会获取 CurrentRunLoop,然后会在其中的DefaultMode 添加了4个 Source0 (需要手动触发的Source), CFMultiplexerSource 是负责各种 Delegate 回调的, CFHTTPCookieStorage 是处理各种 Cookie 的.

当开始网络传输时, 可以看到 NSURLConnection 创建了两个新线程: com.apple.NSURLConnectionLoader 和 com.apple.CFSocket.private. 其中 CFSocket 线程是处理底层socket 连接的. NSURLConnectionLoader 这个线程内部会使用RunLoop来接受底层socket的事件, 并通过之前添加的 Source0 通知到上层的 Delegate.

NSURLConnectionLoader 中的 RunLoop 通过一些基于 mach port 的 Source 接受来自底层 CFSocket 的通知, 当收到通知后, 其会在合适的时机向 CFMultiplexerSource 等  Source0 发送通知, 同时唤醒 Delegate 线程的 RunLoop 来让其处理这些通知.CFMultiplexerSource 会在 Delegate 线程的 RunLoop 对 Delegate 执行实际的回调.

 

RunLoop的 实际应用有 AFNetWorking, AsyncDisplayKit .这两个会在后面单独进行总结和学习.

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值