iOS开发日记24-详解RunLoop
今天博主有一个RunLoop的需求,遇到了一些困难点,在此和大家分享,希望能够共同进步.
RunLoop是什么?
从字面上理解,Runloop指的就是运行循环,iOS中,只要程序启动, 就会创建Runloop,用来处理各种事件(比如定时器事件, 触摸事件等).
它的主要作用有以下几个:
1.保持程序的运行:如果没有它, 程序一启动就over了;
2.事件的处理: 包括按钮点击事件, 屏幕的点击事件等;
3.定时器处理: NSTimer的处理;
4.节约CPU, 提高性能:有任务的时候干活,没有任务时候休息.
在iOS中, 可以通过两套API来访问Runloop:
1.Foundation框架: OC中的框架, 直接访问NSRunLoop对象.
2.Core Foundation框架: c语言的框架, 通过CFRunLoopRef访问.
实际上, NSRunLoop和CFRunLoopRef是同一个对象, 只不过通过不同的API来访问的,NSRunLoop是对CFRunLoopRef的封装.
OC中, 使用下面代码就可以获得Runloop对象:
// Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
// Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
Nslog打印Runloop对象, 里面的内容如下:(由于内容比较多, 只将重点内容列出)
// runloop对象
<CFRunLoop 0x7fb13172abc0 [0x10a5aa180]>{wakeup port = 0x1003, stopped = false, ignoreWakeUps = true,
// 当前模式
current mode = UIInitializationRunLoopMode,
common modes = <CFBasicHash 0x7fb13172ac80
// common modes标记了哪两个模式(后面会讲为啥是标记?)
0 : <CFString 0x10b27ce50 [0x10a5aa180]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x10a586080 [0x10a5aa180]>{contents = "kCFRunLoopDefaultMode"}
}
// source: 源
6 : <CFRunLoopSource 0x7fb131415300 [0x10a5aa180]>
// observers: 观察者
observers = <CFArray 0x7fb13141d300 [0x10a5aa180]>
// timer: 定时器
timers = (null),
// 两种不同的模式
3 : <CFRunLoopMode 0x7fb13172dbb0 [0x10a5aa180]>{name = GSEventReceiveRunLoopMode, port set = 0x2003, timer port = 0x2103,
sources0 = <CFBasicHash 0x7fb13172d260 [0x10a5aa180]>{type = mutable set, count = 1,
entries =>
0 : <CFRunLoopSource 0x7fb13172da30 [0x10a5aa180]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10d8236c8)}}
}
要想知道上面内容是什么意思, 先看下面的内容:
Core Foundation中关于RunLoop的5个类
1.CFRunLoopRef
2.CFRunLoopModeRef
3.CFRunLoopSourceRef
4.CFRunLoopTimerRef
5.CFRunLoopObserverRef
可以这理解为, 一个Runloop对象里面, 必不可少的是CFRunLoopModeRef, Mode里面可以包含timer/source/oberver(只有包含了其中一个, Runloop才能在启动后保证不死)
层级结构如下图:
接下来, 对里面的内容说明一下:
一.CFRunLoopModeRef:代表RunLoop的运行模式
1.一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
2.每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作CurrentMode
3.这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
系统默认注册了5个Mode:
1.kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
2.UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
3.UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
4.GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
5.kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode, 被标记为kCFRunLoopCommonModes格式的都可以成为commonMode, 其中包含1.2两种模式.
二.CFRunLoopSourceRef:是事件源(输入源)
现在主要分为两种:
Source0:非基于Port的
Source1:基于Port的(如GCD底层实现也是通过Port的, 主要用于线程之间的通信)
三.CFRunLoopTimerRef:是基于时间的触发器, 基本上说的就是NSTimer
四.CFRunLoopObserverRef:观察者, 可以用来监听Runloop的状态, 下面列出了Runloop的状态
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理事件源
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Runloop
};
下面图片描述了Runloop的运行和状态:
Runloop的寄生于线程:一个线程只能有唯一对应的runloop;但这个根runloop里可以嵌套子runloops;
自动释放池寄生于Runloop:程序启动后,主线程注册了两个Observer监听runloop的进出与睡觉。一个最高优先级OB监测Entry状态;一个最低优先级OB监听BeforeWaiting状态和Exit状态。
线程(创建)-->runloop将进入-->最高优先级OB创建释放池-->runloop将睡-->最低优先级OB销毁旧池创建新池-->runloop将退出-->最低优先级OB销毁新池-->线程(销毁)
Mach是XNU的内核,进程、线程和虚拟内存等对象通过端口发消息进行通信,Runloop通过mach_msg()函数发送消息,如果没有port 消息,内核会将线程置于等待状态 mach_msg_trap() 。如果有消息,判断消息类型处理事件,并通过modeItem的callback回调。
Runloop有两个关键判断点,一个是通过msg决定Runloop是否等待,一个是通过判断退出条件来决定Runloop是否循环。
应用1:滑动与图片刷新:
当tableview的cell上有需要从网络获取的图片的时候,滚动tableView,异步线程会去加载图片,加载完成后主线程就会设置cell的图片,但是会造成卡顿。可以让设置图片的任务在CFRunLoopDefaultMode下进行,当滚动tableView的时候,RunLoop是在 UITrackingRunLoopMode 下进行,不去设置图片,而是当停止的时候,再去设置图片。
- (void)viewDidLoad {
[super viewDidLoad];
// 只在NSDefaultRunLoopMode下执行(刷新图片)
[self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@""] afterDelay:ti inModes:@[NSDefaultRunLoopMode]];
}
应用2:常驻子线程,保持子线程一直处理事件
为了保证线程长期运转,可以在子线程中加入RunLoop,并且给Runloop设置item,防止Runloop自动退出。
+ (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;
}
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
http://www.cnblogs.com/66it/p/4719701.html?utm_source=tuicool
http://blog.ibireme.com/2015/05/18/runloop/