iOS RunLoop

什么是RunLoop
从字面意思看
运行循环
跑圈

基本作用
保持程序的持续运行
处理App中的各种事件(列如触摸事件、定时器事件、Selector事件)
节省CPU资源,提高程序性能:该做事时做事,该休憩时休憩
……

iOS中有2套API来访问和使用RunLoop

1.Foundation(oc的)里的NSRunLoop
2.Core Foundation(C语言的)里的CFRunLoopRef

NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)

CFRunLoopRef是开源的
https://opensource.apple.com/tarballs/CF/

苹果官方文档

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

每条线程都有唯一的一个与之对应的RunLoop对象

RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value

主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建

RunLoop在第一次”获取时创建”,在线程结束时销毁

获得RunLoop对象

Foundation下
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

Core Foundation下
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

可以在CFRunLoop.c文件里看到如下

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

点击_CFRunLoopGet0看到如下

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); //从字典里通过线程获取runloop
    __CFUnlock(&loopsLock);
    if (!loop) { //发现runloop为空时
    CFRunLoopRef newLoop = __CFRunLoopCreate(t); //创建一个新的runloop
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); //创建好runloop后,保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
        loop = newLoop;
    }
        // don t release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
    CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

Core Foundation中关于RunLoop的5个类
CFRunLoopRef (代表一个RunLoop)
CFRunLoopModeRef (代表RunLoop的运行模式)
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

图解上述关系

|————————————————————————————————————————————————————————————————|
|                         RunLoop                                |
|                                                                |
| | ———————————————————————|   | ———————————————————————|        |
| |       MODE             |   |       MODE             |        |
| |   —————————————————    |   |   —————————————————    |        |
| |   |<Set>Source     |   |   |   |<Set>Source     |   |   ...  |
| |   |<Array>Observer |   |   |   |<Array>Observer |   |        |
| |   |<Array>Timer    |   |   |   |<Array>Timer    |   |        |
| |   ——————————————————   |   |   ——————————————————   |        |
| |————————————————————————|   |————————————————————————|        |
|                                                                |
—————————————————————————————————————————————————————————————————|

CFRunLoopModeRef

CFRunLoopModeRef代表RunLoop的运行模式
一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer
每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入(程序不会退出,只是退出这一次的Loop循环)
这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响

系统默认注册了5个Mode:
kCFRunLoopDefaultMode:App的默认Mode,一般主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,一般用不到
kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode

CFRunLoopTimerRef

CFRunLoopTimerRef是基于时间的触发器
基本上说的就是NSTimer

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和NSDefaultRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

CFRunLoopSourceRef

CFRunLoopSourceRef是事件源(输入源)

按照官方文档,Source的分类
Port-Based Sources
Custom Input Sources
Cocoa Perform Selector Sources

按照函数调用栈,Source的分类
Source0:非基于Port的
Source1:基于Port的,基于内核和其他线程通信、接收、分发系统事件

CFRunLoopObserverRef

CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变

可以监听的时间点有以下几个

typedef CF_OPTIONS(CFOptionFlags,CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),  //1 即将进入runloop
    kCFRunLoopBeforeTimers = (1UL << 1), //2 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), //4 即将处理 Sources
    kCFRunLoopBeforeWaiting = (1UL << 5), //32 即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), //64 刚从休眠中唤醒loop
    kCFRunLoopExit = (1UL << 7), //128 即将退出runloop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

可以往runloop里添加一个observer,监听下loop的状态改变。下面通过回调方式。

// 创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
});

// 添加观察者:监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

// 释放Observer
CFRelease(observer);

RunLoop运行逻辑(网友整理版)


                      1.通知Observer:即将进入Loop  ————————————> Observer
                      ————————————————————————————————————————————
                      | 2.通知Observer:将要处理Timer    ———————————+—————>Observer
                      | 3.通知Observer:将要处理Source0  ———————————+—————>Observer
                      | 4.处理Source0  ———————————————————————————+—————>Source0
                      | 5.如果有Source1,跳到第9步   ————————————————+—————>Source1
Source0(port)         | 6.通知Observer:线程即将休眠   ——————————————+—————>Observer
        Timer  ———————+>7.休眠 ,等待唤醒 ———————————————————————————|
    外部手动唤醒        | 8.通知Observer:线程刚被唤醒  ———————————————+—————>Observer
                      | 9.处理唤醒时收到的消息,之后跳回2 —————————————+—————>Timer、Source1
                      ————————————————————————————————————————————
                     10.通知Observer:即将退出Loop  ————————————> Observer

凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release
列如CFRunLoopObserverCreate
release函数:CFRelease(对象);

延迟执行

//方案一:调用Object方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];

//方案二:调用GCD方法,直接敲dispatch_after就出来了
//dispatch_get_main_queue 可以换成其他队列
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"run-----");
    });

//方案三:调用NSTimer的方法
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];

- (void)run
{
    NSLog(@"run-----");
}

//方案四:sleep
[NSThread sleepForTimeInterval:1.0];//该方法是一种阻塞执行方式,最好放在子线程中执行,否则会影响其他方法的执行。

RunLoop应用

NSTimer
ImageView显示
PerformSelector
常驻线程
自动释放池

// 3秒之后给imageview设置图片,但只在NSDefaultRunLoopMode模式下显示图片,用手滚动textview则不会给imageview设置图片
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@”placeholder”] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];

下面开始常驻线程

第一新建类继承NSThread
#import <Foundation/Foundation.h>

@interface XMGThread : NSThread
@end

#import "XMGThread.h"

@implementation XMGThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

然后我们在控制器里如下写
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //XMGThread继承NSThread,是为了重写dealloc看到线程消亡
    XMGThread *thread; = [[XMGThread alloc] initWithTarget:self selector:@selector(run3) object:nil];
    [thread start];
}

上述方法执行后,每一次点击都是一个新的线程去执行任务,执行完任务线程开始消亡,可以知道开销会很大。那尝试用同一个线程去试试,第一想到的是使用强引用引用thread,不让他消亡,如下

/** 线程对象 */
@property (nonatomic, strong) XMGThread *thread;

- (void)viewDidLoad {
    [super viewDidLoad];
    self.thread = [[XMGThread alloc] initWithTarget:self selector:@selector(run) object:nil];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.thread start];
}
- (void)run
{
    NSLog(@"---------");
}

第一次点击很正常,但第二次点击就奔溃,奔溃打印如下
reason: *** -[XMGThread start]: attempt to start the thread again
意思就是已经消亡的线程不能重新开启。所以即使是强引用,执行完任务后线程还是会消亡。

尝试接着改造,让thread不消亡

/** 线程对象 */
@property (nonatomic, strong) XMGThread *thread;

- (void)viewDidLoad {
    [super viewDidLoad];
    self.thread = [[XMGThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [self.thread start];
}
- (void)run
{
    
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];//给RunLoop添加port,也就是Source,让线程保活。如果RunLoop里面没有Source或Timer,RunLoop还是会结束运行。
    
    [[NSRunLoop currentRunLoop] run]; //创建以及运行循环
    
    //上面一句与下面的两种写法等同。想让一个RunLoop跑圈,第一要选择一种模式,其次模式里的Source或Timer不能为空,不然不会循环。
    //    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    //    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
    
    NSLog(@"---------"); 这句话是不会打印的,由于一直在上一句跑圈,然后执行这个线程里的任务。
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //把self.thread的方法里加了运行循环,这个线程就活下来了,这个方法可以调用。每次点击都在self.thread线程里执行test方法
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)test
{
    NSLog(@"---------");
}

如上已经能做到线程保活了。

还有奇葩的保活方案,如下,改造下上面代码里的run方法, 其他代码照旧。

//这种改造是不行的,该线程一直忙着处理while (1),不会管其他事情的。
- (void)run
{
    while (1); // 当前线程永远在处理这行代码
    
    NSLog(@"---------");
}

- (void)run
{
    while (1) {//一直让NSRunLoop跑圈,当NSRunLoop跑圈时就会检测要不要处理事情,所以比上面单独的while (1);高级许多,也可以做到保活目的。但是不太推荐这种写法。
        [[NSRunLoop currentRunLoop] run];
    }
    
    NSLog(@"---------");
}

NSTimer疑点

@property (nonatomic, strong) XMGThread *thread;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.thread = [[XMGThread alloc] initWithTarget:self selector:@selector(execute) object:nil];
    [self.thread start];
}
//这个方法放到子线程调用
- (void)execute
{
    
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
    //等同于
//    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
//    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
#warning 如果仅仅写上面代码是不起作用的,不是说scheduledTimerWithTimeInterval会自动加入RunLoop吗,是的,但是这里是在子线程,默认没有RunLoop并且不开启。所以上面代码仅仅相当于创建了RunLoop,并给他添加了timer,三部曲里,还差一步就是启动RunLoop
    
#warning 虽然这个定时器是定在NSDefaultRunLoopMode下,但是在textview滚动时依旧能调用(这个textview是界面上添加的,在主线程),由于滚动的那个mode是主运行循环,这个定时器是子线程的运行循环
    
    //子线程里面运行定时器,需要把运行循环开启
    [[NSRunLoop currentRunLoop] run];
}

可以改造下execute方法,让自动释放池包住运行循环

- (void)execute
{
    //最好创建一个自动释放池来包住运行循环
    @autoreleasepool {
        
        [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
        //等同于
        //    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
        //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

        //子线程里面运行定时器,需要把运行循环开启
        [[NSRunLoop currentRunLoop] run];
    }
}

自动释放池什么时候释放?

通过Observer监听RunLoop的状态,一旦监听到RunLoop即将进入睡眠等待状态,就释放自动释放池(kCFRunLoopBeforeWaiting)

GCD定时器

******底层班第一次讲解笔记*****

//对于第一次循环
//while是先判断再执行; 当条件满足进行循环, 反之结束循环
NSInteger i = 10;
while(i < 10){
    i++;
}
NSLog(@"i %ld",i);//最终i的值为10

//do-while是先执行,再判断; 当条件不满足, 结束循环
NSInteger i = 10;
do{
    i++;
}
while(i<10);
NSLog(@"i %ld",i);//最终i的值为11

RunLoop

iOS中有2套API来访问和使用RunLoop
Foundation:NSRunLoop
Core Foundation:CFRunLoopRef

NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是基于CFRunLoopRef的一层OC包装,可以相互转化,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)

CFRunLoopRef是开源的
https://opensource.apple.com/tarballs/CF/

每条线程都有唯一的一个与之对应的RunLoop对象

RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value

线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建

RunLoop会在线程结束时销毁

主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

可以在CFRunLoop.c文件里看到如下

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

点击_CFRunLoopGet0看到如下

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); //从字典里通过线程获取runloop
    __CFUnlock(&loopsLock);
    if (!loop) { //发现runloop为空时
    CFRunLoopRef newLoop = __CFRunLoopCreate(t); //创建一个新的runloop
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); //创建好runloop后,保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
        loop = newLoop;
    }
        // don t release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
    CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

Core Foundation中关于RunLoop的5个类

CFRunLoopRef  (代表一个RunLoop)
CFRunLoopModeRef   (代表RunLoop的运行模式)
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

图解上述关系

|————————————————————————————————————————————————————————————————|
|                         RunLoop                                |
|                                                                |
| | ———————————————————————|   | ———————————————————————|        |
| |       MODE             |   |       MODE             |        |
| |   —————————————————    |   |   —————————————————    |        |
| |   |<Set>Source     |   |   |   |<Set>Source     |   |   ...  |
| |   |<Array>Observer |   |   |   |<Array>Observer |   |        |
| |   |<Array>Timer    |   |   |   |<Array>Timer    |   |        |
| |   ——————————————————   |   |   ——————————————————   |        |
| |————————————————————————|   |————————————————————————|        |
|                                                                |
—————————————————————————————————————————————————————————————————|

CFRunLoopModeRef

CFRunLoopModeRef代表RunLoop的运行模式
一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer
每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响

系统默认注册了5个Mode:
kCFRunLoopDefaultMode:App的默认Mode,一般主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,一般用不到
kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode

typedef struct __CFRunLoop * CFRunLoopRef;

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort;            // used for CFRunLoopWakeUp
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;    /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};

各个名称代表的事件如下 //打断点在输出兰输入 bt ,可打印出部分函数堆栈 eg:(lldb) bt

Source0
触摸事件处理
performSelector:onThread:

Source1
基于Port的线程间通信
系统事件捕捉

Timers
NSTimer
performSelector:withObject:afterDelay:

Observers
用于监听RunLoop的状态
UI刷新(BeforeWaiting)
Autorelease pool(BeforeWaiting)

RunLoop运行逻辑(网友整理版)

                      1.通知Observer:即将进入Loop  ————————————> Observer
                      ————————————————————————————————————————————
                      | 2.通知Observer:将要处理Timer    ———————————+—————>Observer
                      | 3.通知Observer:将要处理Source0  ———————————+—————>Observer
                      | 4.处理Source0  ———————————————————————————+—————>Source0
                      | 5.如果有Source1,跳到第9步   ————————————————+—————>Source1
Source0(port)         | 6.通知Observer:线程即将休眠   ——————————————+—————>Observer
        Timer  ———————+> 7.休眠 ,等待唤醒 —————————————————————————|
    外部手动唤醒        | 8.通知Observer:线程刚被唤醒  ———————————————+—————>Observer
                      | 9.处理唤醒时收到的消息,之后跳回2 —————————————+—————>Timer、Source1
                      ————————————————————————————————————————————
                     10.通知Observer:即将退出Loop  ————————————> Observer

源码讲解了上述流程,但是我没听懂,纯C语言的

******底层班第二次讲解笔记***** (day17、day18)

打断点,然后再控制台输入 bt 就可以看到调用堆栈

GCD和RunLoop关系不大,不过GCD的线程通信的UI刷新,会交给RunLoop管理

什么是RunLoop

运行循环
在程序运行过程中循环做一些事情

RunLoop的基本作用

保持程序的持续运行
处理App中的各种事件(列如触摸事件、定时器事件等)
节省CPU资源,提高程序性能:该做事时做事,该休憩时休憩
……

iOS中有2套API来访问和使用RunLoop

Foundation框架:NSRunLoop
Core Foundation框架:CFRunLoopRef

NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是基于CFRunLoopRef的一层OC包装
CFRunLoopRef是开源的
https://opensource.apple.com/tarballs/CF/

RunLoop与线程

每条线程都有唯一的一个与之对应的RunLoop对象 RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
RunLoop会在线程结束时销毁
主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

获取RunLoop对象

Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

Core Foundation中关于RunLoop的5个类

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

结构如下

typedef struct __CFRunLoop *CFRunLoopRef;

struct __CFRunLoop {
    pthread _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;     //当前mode
    CFMutableSetRef _modes;           //mode合集
}

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
}

CFRunLoopModeRef代表RunLoop的运行模式

一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer

RunLoop启动时只能选择其中一个Mode,作为currentMode

如果需要切换Mode,只能退出当前Loop(退出循环,重新进来),再重新选择一个Mode进入
不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响

如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

含义

Source0
触摸事件处理
performSelector:onThread:

Source1
基于Port的线程间通信
系统事件捕捉

Timers
NSTimer
performSelector:withObject:afterDelay:

Observers
用于监听RunLoop的状态
UI刷新(BeforeWaiting)
Autorelease pool(BeforeWaiting)

常见的2种Mode

kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,一般主线程是在这个Mode下运行

UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响

系统默认注册了5个Mode:
kCFRunLoopDefaultMode:App的默认Mode,一般主线程是在这个Mode下运行。默认模式,在Run Loop没有指定Mode的时候,默认就跑在Default Mode下
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,一般用不到
kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode,而是一个模式集合,当绑定一个事件源到这个模式集合的时候就相当于绑定到了集合内的每一个模式

—–五种Mode—->
Default:NSDefaultRunLoopMode,默认模式,在Run Loop没有指定Mode的时候,默认就跑在Default Mode下
Event tracking:UITrackingRunLoopMode,拖动事件
Connection:NSConnectionReplyMode,用来监听处理网络请求NSConnection的事件
Modal:NSModalPanelRunLoopMode,OS X的Modal面板事件
Common mode:NSRunLoopCommonModes,是一个模式集合,当绑定一个事件源到这个模式集合的时候就相当于绑定到了集合内的每一个模式

可以往runloop里添加一个observer,监听下loop的状态改变。下面通过调方法方式。

- (void)viewDidLoad {
    [super viewDidLoad];
    // 创建Observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
    // 添加Observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 释放
    CFRelease(observer);
}

void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
        default:
            break;
    }
}

RunLoop的运行逻辑

01、通知Observers:进入Loop
02、通知Observers:即将处理Timers
03、通知Observers:即将处理Sources
04、处理Blocks (由于runloop有允许添加block的方法)
05、处理Source0(可能会再次处理Blocks)
06、如果存在Source1,就跳转到第8步
07、通知Observers:开始休眠(等待消息唤醒)(注意:这种休眠也是阻塞的一种形式,只不过跟单纯的阻塞不一样,这个是休眠)
08、通知Observers:结束休眠(被某个消息唤醒)
    01> 处理Timer
    02> 处理GCD Async To Main Queue
    03> 处理Source1
09、处理Blocks
10、根据前面的执行结果,决定如何操作
    01> 回到第02步
    02> 退出Loop
11、通知Observers:退出Loop

源码讲解了上述流程,但是我没听懂,纯C语言的

RunLoop休眠的实现原理????

用户态 内核态

第22天第一节课讲了一点runloop

第24天第二节课讲GCD定时器

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容