美文网首页
iOS --- Runloop

iOS --- Runloop

作者: BabyNeedCare | 来源:发表于2019-06-26 09:22 被阅读0次

    RunLoop基本作用:

    1. 保持程序持续运行

    2.  处理App中的各种事件

    3.  节省CPU资源,提高程序性能

    Runloop, 直译过来就是运行循环。

    那到底Runloop在哪?

    在不做任何手动开启Runloop操作,其实你也能感受到Runloop的存在。

    可能有小伙伴立马跳出来说,你扯蛋,没看见过!!!!!!!

    质疑是对的,那请问如果没有Runloop,当你开启App后,怎样保证主线程不会被销毁,使程序持续运行?

    或者,我们来做个小测试。

    1. 保持程序持续运行

    main.m原文件

    先做小小改动:

    猜猜控制台打印出什么?

    只有第一个NSLog是执行了,另外一个没响应,这能说明UIApplicationMain函数中,开启了一个和主线程相关的RunLoop,导致UIApplicationMain不会返回,一直在运行中,也就保证了程序的持续运行

    2.  处理App中的各种事件

    例如: 触摸事件,定时器事件,Selector事件等。

    为什么这么说? App启动完成后,应该就完事了!!!

    开玩笑,不可能完,还有很多要执行呢,即使没新开线程,在主线程完成,主线程还有任务,那么Runloop就会继续执行其他事件,直到任务完成。

    3. 节省CPU资源,提高程序性能

    程序运行起来时,当什么操作都没有做的时候,RunLoop就告诉CUP,现在没有事情做,我要去休息,这时CUP就会将其资源释放出来去做其他的事情,当有事情做的时候RunLoop就会立马起来去做事情

    从上图看,如果把黑色箭头去掉,那么Runloop不提供Source, Timer, 那么Runloop就休息,线程无需要执行的任务,线程不工作。

    RunLoop对象

    Fundation框架 (基于CFRunLoopRef的封装)

    NSRunLoop对象

    CoreFoundation

    CFRunLoopRef对象

    因为Fundation框架是基于CFRunLoopRef的一层OC封装,这里我们主要研究CFRunLoopRef源码

    如何获得RunLoop对象, 以下前2个使用频率相对较高。

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

    [NSRunLoop mainRunLoop];// 获得主线程的RunLoop对象

    Core FoundationCFRunLoopGetCurrent();// 获得当前线程的RunLoop对象

    CFRunLoopGetMain();// 获得主线程的RunLoop对象

    RunLoop和线程间的关系

    // 拿到当前Runloop 调用_CFRunLoopGet0

    CFRunLoopRef CFRunLoopGetCurrent(void) {

       CHECK_FOR_FORK();

        CFRunLoopRefrl = (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);

        CFMutableDictionaryRefdict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL,&kCFTypeDictionaryValueCallBacks);

        //根据传入的主线程获取主线程对应的RunLoop

        CFRunLoopRefmainLoop = __CFRunLoopCreate(pthread_main_thread_np());

        //保存主线程 将主线程-key和RunLoop-Value保存到字典中

        CFDictionarySetValue(dict,pthreadPointer(pthread_main_thread_np()), mainLoop);

        if(!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)){

           CFRelease(dict);

        }

        CFRelease(mainLoop);

           __CFLock(&loopsLock);

        }

        //从字典里面拿,将线程作为key从字典里获取一个loop

        CFRunLoopRefloop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

       __CFUnlock(&loopsLock);

        //如果loop为空,则创建一个新的loop,所以runloop会在第一次获取的时候创建

        if(!loop) { 

        CFRunLoopRefnewLoop = __CFRunLoopCreate(t);

           __CFLock(&loopsLock);

       loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops,pthreadPointer(t));

        //创建好之后,以线程为key runloop为value,一对一存储在字典中,下次获取的时候,则直接返回字典内的runloop

        if(!loop) {

           CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);

           loop = newLoop;

        }

           // don't release run loops inside the loopsLock, because CFRunLoopDeallocatemay 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);

           }

        }

        returnloop;

    }

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

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

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

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

     RunLoop结构体

    __CFRunLoop结构体

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

    };

    两个成员变量,可能是大家见得比较多的。

    CFRunLoopModeRef_currentMode;

    CFMutableSetRef_modes;

    CFRunLoopModeRef 指向__CFRunLoopMode结构体的指针,

    __CFRunLoopMode结构体源码如下:

    typedef struct __CFRunLoopMode*CFRunLoopModeRef;

    struct __CFRunLoopMode {

       CFRuntimeBase _base;

       pthread_mutex_t _lock;  /* musthave 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 */

    };

    主要查看以下成员变量

    CFMutableSetRef_sources0;

    CFMutableSetRef_sources1;

    CFMutableArrayRef_observers;

    CFMutableArrayRef_timers;

    通过上面分析我们知道,CFRunLoopModeRef代表RunLoop的运行模式,一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer,而RunLoop启动时只能选择其中一个Mode作为currentMode。

    Source1/Source0/Timers/Observer

    1. Source1 : 基于Port的线程间通信

    2. Source0 : 触摸事件,PerformSelectors

    frame #29: 0x000000010d085721 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17,

    SOURCE0:控制台通过“bt”指令打印完整的堆栈信息,由堆栈信息中可以发现,触摸事件,确实是会触发Source0事件。

    NSTimer

    Observer :监听器,用于监听RunLoop的状态

    RunLoop相关类及作用

    CFRunLoopRef - 获得当前RunLoop和主RunLoop

    CFRunLoopModeRef - RunLoop 运行模式,只能选择一种,在不同模式中做不同的操作

    CFRunLoopSourceRef - 事件源,输入源

    CFRunLoopTimerRef - 定时器时间

    CFRunLoopObserverRef - 观察者

    1. CFRunLoopModeRef

    CFRunLoopModeRef代表RunLoop的运行模式

    Source <Set> 在OC里, Set是无序,唯一的多数据集合。

    Observer <Array> 数组

    Timer <Array> 数组

    RunLoop 与 Mode的关系是?

    答:RunLoop一对Mode多;

    Mode里有什么?

    答:Mode包含若干个Source、Timer、Observer

    每次RunLoop启动时,会做什么操作?

    答:指定其中一个 Mode;

    如果需要切换Mode, 能直接切换吗?

    答:不能。只能退出RunLoop,再重新指定一个Mode进入,这样做主要是为了分隔开不同组的Source、Timer、Observer,让其互不影响。如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出。

    RunLoop能否没有Source,Timer?

    答:不能。一种Mode中可以有多个Source(事件源,输入源,基于端口事件源例键盘触摸等) Observer(观察者,观察当前RunLoop运行状态) 和Timer(定时器事件源)。但是必须至少有一个Source或者Timer,因为如果Mode为空,RunLoop运行到空模式不会进行空转,就会立刻退出。

    系统默认注册的5个Mode:

    RunLoop 有五种运行模式,第一,第二种比较常用

    1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行

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

    3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode

    4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到

    5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode

    Mode间的切换

    开发中,经常需要做轮播图,都会加上定时器,当滑动轮播图的时候,NSTimer就会暂停,当我们停止滑动以后,NSTimer又会重新恢复的情况, 这是为什么呢?

    答:NSTimer不管用是因为Mode的切换,因为如果我们在主线程使用定时器,此时RunLoop的Mode为kCFRunLoopDefaultMode,即定时器属于kCFRunLoopDefaultMode,那么此时我们滑动ScrollView时,RunLoop的Mode会切换到UITrackingRunLoopMode,因此在主线程的定时器就不在管用了,调用的方法也就不再执行了,当我们停止滑动时,RunLoop的Mode切换回kCFRunLoopDefaultMode,所以NSTimer就又管用了。

    CFRunLoopSourceRef事件源(输入源)

    Source分为两种

    Source0:非基于Port的 用于用户主动触发的事件(点击button 或点击屏幕)

    Source1:基于Port的 通过内核和其他线程相互发送消息(与内核相关)

    CFRunLoopObserverRef

    能够监听RunLoop的状态改变

    至此,可以看出Observer确实用来监听RunLoop的状态,包括唤醒,休息,以及处理各种事件。

    RunLoop处理逻辑

    RunLoop退出

    1. 主线程销毁RunLoop退出

    2. Mode中有一些Timer 、Source、 Observer,这些保证Mode不为空时,保证RunLoop没有空转并且是在运行的,当Mode中为空的时候,RunLoop会立刻退出

    3. 启动RunLoop的时设置停止时间

    [NSRunLoopcurrentRunLoop]runUntilDate:<#(nonnull NSDate *)#>

    [NSRunLoopcurrentRunLoop]runMode:<#(nonnull NSString *)#> beforeDate:<#(nonnull NSDate *)#>

    RunLoop应用

    1.常驻线程 ---> 给子线程开启一个RunLoop

    注意:

    1) 子线程执行完操作之后就会立即释放,即使我们使用强引用引用子线程使子线程不被释放,也不能给子线程再次添加操作,或者再次开启。

    2)创建子线程相关的RunLoop,在子线程中创建即可,并且RunLoop中要至少有一个Timer 或 一个Source 保证RunLoop不会因为空转而退出,因此在创建的时候直接加入,如果没有加入Timer或者Source,或者只加入一个监听者,运行程序会崩溃

    自动释放池

    Timer和Source是什么?

    答:Timer和Source也是一些变量,需要占用一部分存储空间。

    Timer和Source能否一直保留?

    答:要释放掉,如果不释放掉,就会一直积累,占用的内存也就越来越大,这显然不是我们想要的。

    什么时候释放,怎么释放呢?

    答:RunLoop内部有一个自动释放池,当RunLoop开启时,就会自动创建一个自动释放池,当RunLoop在休息之前会释放掉自动释放池的东西,然后重新创建一个新的空的自动释放池,当RunLoop被唤醒重新开始时,Timer,Source等新的事件就会放到新的自动释放池中,当RunLoop退出的时候也会被释放

    注意:只有主线程的RunLoop会默认启动。也就意味着会自动创建自动释放池,子线程需要在线程调度方法中手动添加自动释放池。

    @autorelease{// 执行代码

     }

    此文部分引用链接: https://www.jianshu.com/p/de752066d0ad

    相关文章

      网友评论

          本文标题:iOS --- Runloop

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