iOS --- Runloop

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容

  • 一.RunLoop简介 运行循环,在程序运行过程中循环做一些事情,如果没有Runloop程序执行完毕就会立即退出,...
    Jt_Self阅读 325评论 0 0
  • 一. RunLoop简介 RunLoop字面意思是跑圈,在我们的项目中其实就是运行循环,而且是充满灵性的死循环,为...
    xx_cc阅读 11,395评论 43 144
  • 一、概述 一般来说,一个线程只能执行一个任务,执行完就会退出,如果我们需要一种机制,让线程能随时处理时间但并不退出...
    一直在路上66阅读 285评论 0 0
  • 深入理解RunLoop:http://www.cocoachina.com/ios/20150601/11970....
    F麦子阅读 378评论 1 2
  • 前言 iOS开发中,RunLoop就是个神秘的领域,很多2~3年的开发者都不能准确的描述它的具体含义,甚至可能从来...
    种代码的小农民阅读 218评论 2 0