二、RunLoop对象
(1)iOS中有两套API来访问和使用RunLoop对象:
(1)Foundation
NSRunLoop
(2)Core Foundation
CFRunLoopRef
(2)NSRunLoop和CFRunLoopRef都代表着RunLoop对象;
(3)NSRunLoop是基于CFRunLoopRef的一层OC的包装,所以要了解着RunLoop的内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面);
(4)RunLoop是字典存储,key是线程,value是runloop;
1、获取RunLoop对象
(1)Foundation
[NSRunLoop currentRunLoop];//获取当前线程的RunLoop对象
[NSRunLoop mainRunLoop];//获取主线程的RunLoop对象
(2)Core Foundation
CFRunLoopGetCurrent();//获取当前线程的RunLoop对象
CFRunLoopGetMain();//获取主线程的RunLoop对象
2、RunLoop相关类
(1)Core Foundation中关于RunLoop的5个类
(1)CFRunLoopRef
(2)CFRunLoopMode
(3)CFRunLoopSourceRef
(4)CFRunLoopTimerRef
(5)CFRunLoopObserverRef
(2)CFRunLoopMode
1432798883604537.png
(1)CFRunLoopMode代表RunLoop的运行模式
(2)一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
(3)每次RunLoop启动时,只能呢指定其中一个Mode,这个Mode被称为CurrentMode
(4)如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入,这么做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响;
(5)系统默认注册了5个Mode:
kCFRunLoopDefaultMode :App的默认Mode,通常主线程是在这个Mode下运行;
UITrackingRunLoopMode :界面跟踪Mode,用于scrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响;
UIInitializationRunLoopMode :在启动App时进入的第一个Mode,启动完成后就不再使用;
GSEventReceiveRunLoopMode :接受系统事件的内部Mode,通常用不到;
kCFRunLoopCommonModes :这是一个用于占位用的Mode,不是一种真正的 Mode;
(3) CFRunLoopTimerRef
CFRunLoopTimerRef是基于时间的触发器,基本上说的就是NSTimer:
NSTimer*timer=[NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//只运行在NSDefaultRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//只运行在UITrackingRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//定时器会跑在标记为common modes的模式下
//标记为common modes的模式 :UITrackingRunLoopMode和kCFRunLoopDefaultMode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//调用了scheduledTimer返回的定时器,已经自动被添加到当前的runLoop中,而且是NSDefaultRunLoopMode
NSTimer*timer=[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//修改模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
那么在滑动页面上的列表时, timer 会暂定回调,为什么?如何解决?
原因:
timer 默认是运行在 NSDefaultRunLoopMode 模式下的,当滑动页面上的列表时,进入了 UITrackingRunLoopMode 模式,这时候 timer 就会停止。
办法:
修改 timer 的运行模式为 NSRunLoopCommonModes,这样定时器就可以一直运行了。
补充:
1》在子线程中通过 scheduledTimerWithTimeInterval:...方法来构建NSTimer,方法内部已经创建 NSTimer 对象,并加入到 RunLoop 中,运行模式为NSDefaultRunLoopMode,由于 Mode 有 timer 对象,所以 RunLoop 就开始监听定时器事件了,从而开始进入运行循环,这个方法仅仅是创建 RunLoop 对象,并不会主动启动 RunLoop,需要再调用 run方法来启动;
2》如果在主线程中通过 scheduledTimerWithTimeInterval:...方法来构建 NSTimer,就不需要主动启动 RunLoop 对象,因为主线程的 RunLoop 对象在程序运行起来就已经被启动了;
//userInfo参数:用来给NSTimer的userInfo 属性赋值,userInfo 是只读的,只能在构建 NSTimer 对象时赋值
[NSTimer scheduledTimerWithTimeInterval:1.0 target:selfselector:@selector(run:) userInfo:@"ya 了 个 hoo"repeats:YES];
// scheduledTimer...方法创建出来 NSTimer 虽然已经指定了默认模式,但是【允许你修改模式】
[[NSRunLoop currentRunLoop] addTimer:timerforMode:NSRunLoopCommonModes];
// 【仅在子线程】需要手动启动 RunLoop 对象,进入运行循环
[[NSRunLoop currentRunLoop] run];
(4)CFRunLoopSourceRef
CFRunLoopSourceRef是事件源(输入源)。
按照官方文档,Source的分类:
(1)Port-Based Sources
(2)Custom Input Sources
(3)Cocoa Perform Selector Sources
按照函数调用栈,Source的分类:
(1)Source() :非基于Port的
(2)Source1 :基于Port的,通过内核和其他线程通信,接收,分发系统事件
(5)CFRunLoopObserverRef
CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变。
可以监听的时间点有以下几个:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),//1 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1),// 2即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2),// 4即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5),//32 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 64刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7),// 128即将退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
//添加observer
CFRunLoopObserverRef observer=CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(),kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"监听到RunLoop状态的状态发生改变---%zd",activity);
});
//添加观察者:监听RunLoop状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(),observer, kCFRunLoopDefaultMode);
//释放observer
CFRelease(observer);
补充:
CF的内存管理(Core Foundation):
(1)凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象、都需要在最后做一次release;
(2)release函数: CFRelease(对象);