RunLoop

一.基本概念及用途

1.runloop是什么?

1)字面意思:跑圈.
2)runloop是一个do while 循环,
3)保证程序持续运行,
4)处理App的各种事件(比如触摸,定时器,Selector事件)
5)节省cpu资源,提高程序性能.(休眠,唤醒)


2.runloop应用于什么?

1)使线程不被销毁,常驻内存
2)在runloop 即将休眠的时候做一些其他操作
3)在子线程中开启一个定时器
4)在子线程做一些长期的监控


3.runloop与线程关系

1)主线程runloop 默认创建开启
2)子线程的runloop需要手动创建
3)runloop 在线程中获取是创建,在线程结束时销毁
4)每条线程都有与之一一对应的runloop对象


二.runloop相关的5个类

RunLoop_第1张图片
683B1ED5-C048-4429-9887-223490E29BE2.png
1.runloop相关的5个类

CFRunLoopRef: runloop对象
CFRunLoopMode : runloop 的运行模式
CFRunLoopTimerRef : runloop 的计时器对象
CFRunLoopObserverRef : runloop 观察者
CFRunLoopSourceRef : runloop 输入源,事件源

2.runloop对象获取

NSFoundation
[NSRunLoop currentRunLoop] //获取当前线程的runloop 对象
[NSRunLoop mainRunLoop]//获取当主线程的runloop 对象

Core Foundation
CFRunLoopGetCurrent() //获取当前线程的runloop 对象
CFRunLoopGetMain()//获取当主线程的runloop 对象

我们更多的使用是Core Foundation,因为Core Foundation可以获取到RunLoopObserver从而监听runloop 的运行状态

    NSLog(@"%@-----%@",CFRunLoopGetCurrent(),[NSRunLoop currentRunLoop]);
    
//    
//    

3.runloopMode

runloop 的有5种运行模式,每次只能指定一个Mode
NSDefaultRunLoopMode :App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode :界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
NSRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode
UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后不再使用
GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到


4.runloopTimer

顾名思义就是定时器的意思
NSTimer 受 runloop 影响,它只是跑在对应的mode 上(NSDefaultRunLoopMode 或者 UITrackingRunLoopMode),如果想跑在两者上需要将定时器添加到NSRunLoopCommonModes,所以我们用计时器的时候需要注意加在哪个Mode上.
** 另一种定时器 GCD

//.创建定时器
    //第一个参数:source类型-> DISPATCH_SOURCE_TYPE_TIMER 代表定时器
    //第二个参数:描述信息,线程ID
    //第三个参数:更详细的信息
    //第四个参数:决定在哪个线程中执行
    //dispatch_source_t 带* 的结构体,需要强引用timer,否则不会执行
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    
    //.设置定制器
    //第一个参数:定时器对象
    //第二个参数:从现在开始
    //第三个参数:多久间隔
    //第四个参数:精准度,绝对精准 0
    dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    
    //.执行回调
    dispatch_source_set_event_handler(self.timer, ^{
        [self test];
    });
    
    //启动
    dispatch_resume(self.timer);

相比timer :
1.不受runloop 影响
2.绝对精准


5.runloopObserver

runloopObserver:监听者,观察者
监听runloop运行状态,获取监听者需要Core Foundation

//.创建observer
    //第一个参数:分配内存方式
    //第二个参数:监听状态
    //第三个参数:总是监听传YES
    //第四个参数:优先级,总是传0
    //第五个参数:监听回调
    
    CFRunLoopObserverRef Observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry: //即将进入runloop
                NSLog(@"即将进入runloop");
                break;
            case kCFRunLoopBeforeTimers: //即将处理timer
                NSLog(@"即将处理timer");
                break;
            case kCFRunLoopBeforeSources: //即将处理source
                NSLog(@"即将处理source");
                break;
            case kCFRunLoopBeforeWaiting: //即将进入休眠
                NSLog(@"即将进入休眠");
                break;
            case kCFRunLoopAfterWaiting: //被唤醒
                NSLog(@"被唤醒");
                break;
            case kCFRunLoopExit: //runloop退出
                NSLog(@"runloop退出");
                break;
                
            default:
                break;
        }
    });
    
    //加入到当前runloop
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), Observer, kCFRunLoopDefaultMode);
    
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    
    CFRunLoopRun();

因为在子线程中开启runloop 至少需要一个timer 或者 source ,runloop 才会启动. addPort 是比添加timer 更好的方式


6.runloopSource

runloopSource:事件源,输入源
有两种source:
source0 : 非基于port,手动触发的事件
source1 : 基于port ,属于系统级的


三.看看代码

先看看runloop的源码
源码地址:https://opensource.apple.com/source/CF/CF-855.14/
找到CFRunLoop.c

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    //如果线程不存在,默认是主线程
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFSpinUnlock(&loopsLock);
        //创建一个全局的字典
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        //将mainLoop 存入到字典中
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }
    //根据线程获取一个loop,__CFRunLoops是以一个子线程的字典 区别于 上面的 dict
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
        //没有loop,创建一个
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        //如果__CFRunLoops 没有与之子线程对应的loop,那么将newLoop 存入到__CFRunLoops
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFSpinUnlock(&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;
}

四.运行图

RunLoop_第2张图片
D51CCB07-8DD1-4C08-975E-E451CAAE0CB8.png

以上是对runloop 的总结,对于子线程常驻问题,有一个好处是节省线程的创建销毁的开销,这个代码其实在 5.runloopObserver 中已经有了,以后有新的我会补充进来

你可能感兴趣的:(RunLoop)