通常来讲,一个线程只能执行一个任务,执行完就会退出,若是咱们须要一种机制,让线程能随时处理时间但并不退出,那么 RunLoop 就是这样的一个机制。Runloop是事件接收和分发机制的一个实现。objective-c
RunLoop其实是一个对象,这个对象在循环中用来处理程序运行过程当中出现的各类事件(好比说触摸事件、UI刷新事件、定时器事件、Selector事件),从而保持程序的持续运行;并且在没有事件处理的时候,会进入睡眠模式,从而节省CPU资源,提升程序性能。编程
简单的说run loop是事件驱动的一个大循环,以下代码所示:bash
int main(int argc, char * argv[]) {
//程序一直运行状态
while (AppIsRunning) {
//睡眠状态,等待唤醒事件
id whoWakesMe = SleepForWakingUp();
//获得唤醒事件
id event = GetEvent(whoWakesMe);
//开始处理事件
HandleEvent(event);
}
return 0;
}
复制代码
程序一启动就会开一个主线程,主线程一开起来就会跑一个主线程对应的Runloop, Runloop保证主线程不会被销毁,也就保证了程序的持续运行。不光iOS,在其余的编程平台,Android, Windows等都有一个相似Runloop的机制保证程序的持续运行。网络
GCD, mach kernel, block, pthread并发
NSTimer, UIEvent, Autorelease, NSObject(NSDelayedPerforming), NSObject(NSThreadPerformAddition), CADisplayLink, CATransition, CAAnimation, dispatch_get_main_queue() (GCD 中dispatch到main queue的block会被dispatch到main Runloop中执行), NSPort, NSURLConnection, AFNetworking(这个第三方网络请求框架使用在开启新线程中添加本身到Runloop监听事件)app
程序运行起来时,当什么操做都没有作的时候,Runloop告诉CPU, 如今没有事情作,我要去休息, 这时CPU就会将资源释放出来去作其余的事情,当有事情作的时候Runloop就会立马起来去作事情。框架
iOS 程序的入口是 main 函数异步
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
复制代码
程序主线程一开起来,就会跑一个和主线程对应的Runloop, 那么Runloop必定是在程序的入口main函数中开启。async
堆栈最底层是start(dyld),往上依次是main,UIApplication(main.m) -> GSEventRunModal(Graphic Services) -> RunLoop(包含CFRunLoopRunSpecific,__CFRunLoopRun,__CFRunLoopDoSouces0,CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION) -> Handle Touch Event函数
CFRunLoop开源代码:http://opensource.apple.com/source/CF/CF-855.17/
Runloop 源码:
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
复制代码
咱们发现RunLoop确实是do while经过判断result的值实现的。所以,咱们能够把RunLoop当作一个死循环。若是没有RunLoop,UIApplicationMain函数执行完毕以后将直接返回,也就没有程序持续运行一说了。
执行顺序的伪代码:
int32_t __CFRunLoopRun()
{
// 通知即将进入runloop
__CFRunLoopDoObservers(KCFRunLoopEntry);
do
{
// 通知将要处理timer和source
__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(kCFRunLoopBeforeSources);
// 处理非延迟的主线程调用
__CFRunLoopDoBlocks();
// 处理Source0事件
__CFRunLoopDoSource0();
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks();
}
/// 若是有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 而后跳转去处理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort();
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
// GCD dispatch main queue
CheckIfExistMessagesInMainDispatchQueue();
// 即将进入休眠
__CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);
// 等待内核mach_msg事件
mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts();
// 等待。。。
// 从等待中醒来
__CFRunLoopDoObservers(kCFRunLoopAfterWaiting);
// 处理因timer的唤醒
if (wakeUpPort == timerPort)
__CFRunLoopDoTimers();
// 处理异步方法唤醒,如dispatch_async
else if (wakeUpPort == mainDispatchQueuePort)
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
// 处理Source1
else
__CFRunLoopDoSource1();
// 再次确保是否有同步的方法须要调用
__CFRunLoopDoBlocks();
} while (!stop && !timeout);
// 通知即将退出runloop
__CFRunLoopDoObservers(CFRunLoopExit);
}
复制代码
RunLoop对象包括Fundation中的NSRunLoop对象和CoreFoundation中的CFRunLoopRef对象。由于Fundation框架是基于CFRunLoopRef的封装,所以咱们学习RunLoop仍是要研究CFRunLoopRef 源码。
//Foundation
[NSRunLoop currentRunLoop]; // 得到当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 得到主线程的RunLoop对象
//Core Foundation
CFRunLoopGetCurrent(); // 得到当前线程的RunLoop对象
CFRunLoopGetMain(); // 得到主线程的RunLoop对象
复制代码
值的注意的是子线程中的runloop不是默认开启的,须要手动开启,当调用 [NSRunLoop currentRunLoop] 时,若已存在当前线程的runloop返回,若不存在建立一个新的runloop对象再返回。
CFRunloopRef 源码
// 建立字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 建立主线程 根据传入的主线程建立主线程对应的RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主线程 将主线程-key和RunLoop-Value保存到字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
复制代码
Apple 不容许直接建立Runloop, 它只提供了两个自动获取的函数: CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。 CFRunLoopRef源码:
/// 用DefaultMode启动
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode启动,容许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
/// RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
/// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 若是mode里没有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 内部函数,进入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 触发 Source0 (非port) 回调。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 若是有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 而后跳转去处理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
/// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
/// • 一个基于 port 的Source 的事件。
/// • 一个 Timer 到时间了
/// • RunLoop 自身的超时时间到了
/// • 被其余什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
/// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
/// 收到消息,处理消息。
handle_msg:
/// 9.1 若是一个 Timer 到时间了,触发这个Timer的回调。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 9.2 若是有dispatch到main_queue的block,执行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
/// 9.3 若是一个 Source1 (基于port) 发出事件了,处理这个事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 执行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 进入loop时参数说处理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部调用者强制中止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
/// 若是没超时,mode里没空,loop也没被中止,那继续loop。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
复制代码
能够看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚建立时并无 RunLoop,若是你不主动获取,那它一直都不会有。RunLoop 的建立是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。
[NSRunLoop currentRunLoop];方法调用时,会先看一下字典里有没有存子线程相对用的RunLoop,若是有则直接返回RunLoop,若是没有则会建立一个,并将与之对应的子线程存入字典中。
Core Foundation中关于RunLoop的5个类:
CFRunLoopRef //得到当前RunLoop和主RunLoop
CFRunLoopModeRef //运行模式,只能选择一种,在不一样模式中作不一样的操做
CFRunLoopSourceRef //事件源,输入源
CFRunLoopTimerRef //定时器时间
CFRunLoopObserverRef //观察者
复制代码
一个Runloop包含若干个Mode, 每一个Mode又包含若干个Source / Timer / Observer. 每次调用Runloop 的主函数时,只能指定其中一个Mode, 这个Mode被称做 CurrentMode. 若是须要切换Mode, 只能退出Loop, 再从新指定一个Mode进入。这样作主要是为了分隔开不一样组的 Source/Timer/Observer, 让其互不影响。
系统默认注册了 5 个Mode, 其中常见的有第 1,2 种:
1. kCFRunLoopDefaultMode:App的默认Mode,一般主线程是在这个Mode下运行
2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其余 Mode 影响
3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就再也不使用
4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,一般用不到
5. kCFRunLoopCommonModes: 这是一个占位用的Mode,做为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并非一种真正的Mode
复制代码
上面的Source/Timer/Observer 被统称为 model item, 一个item 能够被同时加入多个 Mode. 但一个item被重复加入同一个mode时是不会有效果的。若是一个mode中一个item都没有,则Runloop会直接退出,不进入循环。
Mode 间切换 咱们平时在开发中必定遇到过,当咱们使用NSTimer每一段时间执行一些事情时滑动UIScrollView,NSTimer就会暂停,当咱们中止滑动之后,NSTimer又会从新恢复的状况,咱们经过一段代码来看一下:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
// 加入到RunLoop中才能够运行
// 1. 把定时器添加到RunLoop中,而且选择默认运行模式NSDefaultRunLoopMode = kCFRunLoopDefaultMode
// [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 当textFiled滑动的时候,timer失效,中止滑动时,timer恢复
// 缘由:当textFiled滑动的时候,RunLoop的Mode会自动切换成UITrackingRunLoopMode模式,所以timer失效,当中止滑动,RunLoop又会切换回NSDefaultRunLoopMode模式,所以timer又会从新启动了
// 2. 当咱们将timer添加到UITrackingRunLoopMode模式中,此时只有咱们在滑动textField时timer才会运行
// [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// 3. 那个如何让timer在两个模式下均可以运行呢?
// 3.1 在两个模式下都添加timer 是能够的,可是timer添加了两次,并非同一个timer
// 3.2 使用站位的运行模式 NSRunLoopCommonModes标记,凡是被打上NSRunLoopCommonModes标记的均可以运行,下面两种模式被打上标签
//0 : <CFString 0x10b7fe210 [0x10a8c7a40]>{contents = "UITrackingRunLoopMode"}
//2 : <CFString 0x10a8e85e0 [0x10a8c7a40]>{contents = "kCFRunLoopDefaultMode"}
// 所以也就是说若是咱们使用NSRunLoopCommonModes,timer能够在UITrackingRunLoopMode,kCFRunLoopDefaultMode两种模式下运行
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
NSLog(@"%@",[NSRunLoop mainRunLoop]);
}
-(void)show
{
NSLog(@"-------");
}
复制代码
由上述代码能够看出,NSTimer无论用是由于Mode的切换,由于若是咱们在主线程使用定时器,此时RunLoop的Mode为kCFRunLoopDefaultMode,即定时器属于kCFRunLoopDefaultMode,那么此时咱们滑动ScrollView时,RunLoop的Mode会切换到UITrackingRunLoopMode,所以在主线程的定时器就不在管用了,调用的方法也就再也不执行了,当咱们中止滑动时,RunLoop的Mode切换回kCFRunLoopDefaultMode,全部NSTimer就又管用了。
使用GCD也能够建立计时器,并且更为精确:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//建立队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//1.建立一个GCD定时器
/*
第一个参数:代表建立的是一个定时器
第四个参数:队列
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 须要对timer进行强引用,保证其不会被释放掉,才会按时调用block块
// 局部变量,让指针强引用
self.timer = timer;
//2.设置定时器的开始时间,间隔时间,精准度
/*
第1个参数:要给哪一个定时器设置
第2个参数:开始时间
第3个参数:间隔时间
第4个参数:精准度 通常为0 在容许范围内增长偏差可提升程序的性能
GCD的单位是纳秒 因此要*NSEC_PER_SEC
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
//3.设置定时器要执行的事情
dispatch_source_set_event_handler(timer, ^{
NSLog(@"---%@--",[NSThread currentThread]);
});
// 启动
dispatch_resume(timer);
}
复制代码
Source分为两种:
Source0:非基于Port的 用于用户主动触发的事件(点击button 或点击屏幕) Source1:基于Port的 经过内核和其余线程相互发送消息(与内核相关) 注意:Source1在处理的时候会分发一些操做给Source0去处理
NSTimer是对RunLoopTimer的封装
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;
复制代码
CFRunLoopObserverRef是观察者,可以监听RunLoop的状态改变。 咱们直接来看代码,给RunLoop添加监听者,监听其运行状态:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//建立监听者
/*
第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配
第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听全部状态
第三个参数 Boolean repeats:YES:持续监听 NO:不持续
第四个参数 CFIndex order:优先级,通常填0便可
第五个参数 :回调 两个参数observer:监听者 activity:监听的事件
*/
/*
全部事件
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop进入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop要处理Timers了");
break;
case kCFRunLoopBeforeSources:
NSLog(@"RunLoop要处理Sources了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop要休息了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"RunLoop醒来了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出了");
break;
default:
break;
}
});
// 给RunLoop添加监听者
/*
第一个参数 CFRunLoopRef rl:要监听哪一个RunLoop,这里监听的是主线程的RunLoop
第二个参数 CFRunLoopObserverRef observer 监听者
第三个参数 CFStringRef mode 要监听RunLoop在哪一种运行模式下的状态
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
/*
CF的内存管理(Core Foundation)
凡是带有Create、Copy、Retain等字眼的函数,建立出来的对象,都须要在最后作一次release
GCD原本在iOS6.0以前也是须要咱们释放的,6.0以后GCD已经归入到了ARC中,因此咱们不须要管了
*/
CFRelease(observer);
}
复制代码
运行结果:
[NSRunLoop currentRunLoop]runUntilDate:<#(nonnull NSDate *)#>
[NSRunLoop currentRunLoop]runMode:<#(nonnull NSString *)#> beforeDate:<#(nonnull NSDate *)#>
复制代码
NSTimer无论用是由于Mode的切换,由于若是咱们在主线程使用定时器,此时RunLoop的Mode为kCFRunLoopDefaultMode,即定时器属于kCFRunLoopDefaultMode,那么此时咱们滑动ScrollView时,RunLoop的Mode会切换到UITrackingRunLoopMode,所以在主线程的定时器就不在管用了,调用的方法也就再也不执行了,当咱们中止滑动时,RunLoop的Mode切换回kCFRunLoopDefaultMode,全部NSTimer就又管用了。若想定时器继续执行,须要将NSTimer 注册为 kCFRunLoopCommonModes 。
当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会建立一个 Timer 并添加到当前线程的 RunLoop 中。因此若是当前线程没有 RunLoop,则这个方法会失效。 当调用 performSelector:onThread: 时,实际上其会建立一个 Timer 加到对应的线程去,一样的,若是对应线程没有 RunLoop 该方法也会失效。
事件响应: 苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。 当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给须要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。 _UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。一般事件好比 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。
手势识别: 当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。 苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取全部刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。 当有 UIGestureRecognizer 的变化(建立/销毁/状态改变)时,这个回调都会进行相应处理。
当在操做 UI 时,好比改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。
苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个很长的函数:_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历全部待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。因此说界面刷新并不必定是在setNeedsLayout相关的代码执行后马上进行的。
系统就是经过@autoreleasepool {}这种方式来为咱们建立自动释放池的,一个线程对应一个runloop,系统会为每个runloop隐式的建立一个自动释放池,全部的autoreleasePool构成一个栈式结构,在每一个runloop结束时,当前栈顶的autoreleasePool会被销毁,并且会对其中的每个对象作一次release(严格来讲,是你对这个对象作了几回autorelease就会作几回release,不必定是一次),特别指出,使用容器的block版本的枚举器的时候,系统会自动添加一个autoreleasePool
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
复制代码
首先引入一个概念:Event_loop,通常一个线程执行完任务后就会退出,当须要保证该线程不退出,能够经过相似如下方式:
function do_loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
复制代码
开启一个循环,保证线程不退出,这就是Event_loop模型。这是在不少操做系统中都使用的模型,例如OS/iOS中的RunLoop。这种模型最大的做用就是管理事件/消息,在有新消息到来时马上唤醒处理,没有待处理消息时线程休眠,避免资源浪费。
使用NSOperation+NSURLConnection并发模型都会面临NSURLConnection下载完成前线程退出致使NSOperation对象接收不到回调的问题。AFNetWorking解决这个问题的方法是按照官方的guid NSURLConnection 上写的NSURLConnection的delegate方法须要在connection发起的线程runloop中调用,因而AFNetWorking直接借鉴了Apple本身的一个Demo的实现方法单独起一个global thread,内置一个runloop,全部的connection都由这个runloop发起,回调也是它接收,不占用主线程,也不耗CPU资源。
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread =
[[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
复制代码
相似的能够用这个方法建立一个常驻服务的线程。
利用CFRunLoopMode的特性,能够将图片的加载放到NSDefaultRunLoopMode的mode里,这样在滚动UITrackingRunLoopMode这个mode时不会被加载而影响到。
UIImage *downloadedImage = ...;
[self.imageView performSelector:@selector(setImage:)
withObject:downloadedImage
afterDelay:0
inModes:@[NSDefaultRunLoopMode]];
复制代码
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));
while (1) {
for (NSString *mode in allModes) {
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
复制代码
- (BOOL)runUntilBlock:(BOOL(^)())block timeout:(NSTimeInterval)timeout
{
__block Boolean fulfilled = NO;
void (^beforeWaiting) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) =
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
fulfilled = block();
if (fulfilled) {
CFRunLoopStop(CFRunLoopGetCurrent());
}
};
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, beforeWaiting);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// Run!
CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false);
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
CFRelease(observer);
return fulfilled;
}
复制代码