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/
posted @ 2015-09-23 10:37  Mr.林的博客  阅读(674)  评论(0编辑  收藏  举报