RunLoop

作者: 我是C | 来源:发表于2017-12-22 12:08 被阅读55次

    一.基本概念及用途

    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个类

    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]);
        
    //    <CFRunLoop 0x6000001e8f00 [0x1113329b0]>
    //    <CFRunLoop 0x6000001e8f00 [0x1113329b0]>
    

    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;
    }
    

    四.运行图

    D51CCB07-8DD1-4C08-975E-E451CAAE0CB8.png

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

    相关文章

      网友评论

          本文标题:RunLoop

          本文链接:https://www.haomeiwen.com/subject/xhctgxtx.html