[ios]浅谈nsrunloop工作原理和相关应用

author author     2022-08-01     206

关键词:

一. 认识NSRunloop

 1.1 NSRunloop与程序运行

   那么具体什么是NSRunLoop呢?其实NSRunLoop的本质是一个消息机制的处理模式。让我们首先来看一下程序的入口——main.m文件,一个ios程序启动后,只有短短的十行代码居然能保持整个应用程序一直运行而没有退出,是不是有点意思?程序之所以没有直接退出是因为UIApplicationMain这个函数内部默认启动了一个跟主线程相关的NSRunloop对象UIApplicationMain这个函数一直执行没有返回就保存程序一直运行的状态。

 1 #import <UIKit/UIKit.h>
 2 
 3 #import "AppDelegate.h"
 4 
 5 int main(int argc, char * argv[]) {
 6 
 7     @autoreleasepool {
 8 
 9         return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
10 
11     }
12 
13 }

 

  文章之初我们暂且将NSRunloop理解为实现这样功能的一段代码 , 这可以帮助我们更好的理解NSRunloop处理事件的过程(实际上远比这复杂的多:))。

 1 int main(int argc, char * argv[]) {
 2 
 3   BOOL runnning =YES;
 4    do{
 5         ...
 6        //处理各种操作 各种事件
 7        ...
 8       }while(running);
 9   
10     return 0;
11 }    

 

 

   下面用官方提供的一幅非常经典的图片,来认识NSRunloop循环处理时间的流程。

       技术分享

   通过所有的“消息”都被添加到了NSRunLoop中去,而在这里这些消息并分为“input source”和“Timer source” 并在循环中检查是不是有事件需要发生,如果需要那么就调用相应的函数处理。由此形成了运行->检测->休眠 ->运行 的循环状态。

    

 

 1.1 NSRunloop与线程之间关系的解析

 

   简单说,一条线程对应一个NSRunloop对象。主线程NSRunloop对象是默认开启的,其他线程的NSRunloop对象需要手动获取。其实NSRunloop对象是懒加载的,所以不需要实例化这个类,而是直接调用获取线程Runloop的方法即可唤醒。Runloop在第一次获取时创建,在线程结束时销毁。保持NSRunloop一直存在的方法稍后介绍。

 

  1.1.1  获得NSRunloop对象的方法

  iOS其实有两套Api来访问和使用Runloop , NSRunloop是对CFRunloopRef的进一步封装,并且CFRunloopRef是线程安全的,而这一点NSRunloop并不能保证。

  Foundation ->NSRunloop

   获得当前线程的Runloop的方法  [NSRunloop currentRunloop];

   获得主线程的Runloop的方法 [NSRunloop  mainRunloop];

 

  Core Foundation ->CFRunloopRef

  CFRunloopGetCurrent();

  CFRunloopGetMain();

 

  由苹果官方文档可以看出线程和Runloop对象的对应关系。如果你仔细阅读可以从代码中可以看出runloop的存储方式是字典,而且key是线程。

 1 // should only be called by Foundation
 2 // t==0 is a synonym for "main thread" that always works
 3 
 4 //函数返回值为CFRunLoopRef  形参类型为pthread_t 根据线程创建runloop对象
 5 
 6 CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
 7 
 8     if (pthread_equal(t, kNilPthreadT)) {
 9 
10 t = pthread_main_thread_np();
11 
12     }
13 
14     __CFLock(&loopsLock);
15 
16     if (!__CFRunLoops) {
17 
18         __CFUnlock(&loopsLock);
19 
20 CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
21 
22 //由此句可得出  调用其他线程NSRunloop对象也会首先创建主线程NSRunloop对象
23 CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
24 
25 CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
26 
27 if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
28 
29     CFRelease(dict);
30 
31 }
32 
33 CFRelease(mainLoop);
34 
35         __CFLock(&loopsLock);
36 
37     }
38 
39     CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
40 
41     __CFUnlock(&loopsLock);
42 
43     if (!loop) {
44 
45 CFRunLoopRef newLoop = __CFRunLoopCreate(t);
46 
47         __CFLock(&loopsLock);
48 
49 loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
50 
51 if (!loop) {
52 
53     CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
54 
55     loop = newLoop;
56 
57 }
58
59         // don‘t release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
60 
61         __CFUnlock(&loopsLock);
62 
63 CFRelease(newLoop);
64 
65     }
66 
67     if (pthread_equal(t, pthread_self())) {
68 
69         _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
70 
71         if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
72 
73             _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
74 
75         }
76     }
77     return loop;
78 }

 

   1.1.2  Mode

  

  Mode中有三个非常重要的组成部分,Timer(定时器)、 Source(事件源) 以及Observor(观察者)。一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。首先要指出的是一个runloop启动时必须指定一个Mode  , 并且这个Mode被称为currentMode 。如果要切换Mode,只能退出runloop重新进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响随后我们会分别介绍每一类的具体作用与应用场景。

  

 

  系统默认注册的Mode有五种

    kCFRunloopDefaultMode    // App默认Mode   通常主线程是在这个mode下运行

    UITrackingRunloopMode    // 界面跟踪Mode  用于scrollView追踪触摸  界面滑动时不受其他Mode影响

    UIinitializationRunloopMode    //app一启动进入的第一个Mode,启动完成后就不再使用

    GSEventRecieveRunloopMode   //苹果使用绘图相关

    NSRunLoopCommonModes   //占位模式

  

 

  1.1.2.1 CFRunloopTimerRef 基于时间的触发器

   NSTimer

  首先说一下NSTimer,一个NSRunloop可以创建多个Timer。因为定时器只会运行在指定Mode ,一旦Runloop进入其他模式, 定时器就不会工作了。

  NSTimer的创建方法 

 [NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)#> target:<#(nonnull id)#> selector:<#(nonnull SEL)#> userInfo:<#(nullable id)#> repeats:<#(BOOL)#>]

  该方法默认添加到当前runloop,并Mode为kCFRunloopDefaultMode

 

1  NSTimer * timer =[NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
2 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; //手动添加到runloop 可以指定Mode

  这样声明的NSTimer可以解决在滑动scrollView时NSTimer不工作的问题。forMode:NSRunLoopCommonModes的意思为,定时器可以运行在标记为common modes模式下。具体包括两种: kCFRunloopDefaultMode     UITrackingRunloopMode。

 

  GCD定时器

  GCD定时器的优点有很多,首先不受Mode的影响,Runloop受Mode影响时常不能正常工作,除此之外GCD的精确度明显高于NSTimer,这些优点让我们有必要了解GCD定时器这种方法。

 

   1.1.2.2  CFRunloopSourceRef  事件源(输入源)

 

  按照苹果官方文档,Source分类

  Port-Based  Sources  基于端口的  和其他线程 或者内核

  Custom Input  Sources  

  Cocoa   Perform   Selector  Sources  

 

  按照函数调用栈来分类   

  Source0 :   非基于Port

  Source1:   基于port的,通过内核 和其他线程通信,接收、分发事件。

 

 

 

   1.1.2.3  CFRunloopObservorRef  观察者监听runloop状态改变

 

 1 /* Run Loop Observer Activities */
 2 
 3 typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
 4 
 5     kCFRunLoopEntry = (1UL << 0),
 6 
 7     kCFRunLoopBeforeTimers = (1UL << 1),
 8 
 9     kCFRunLoopBeforeSources = (1UL << 2),
10 
11     kCFRunLoopBeforeWaiting = (1UL << 5),
12 
13     kCFRunLoopAfterWaiting = (1UL << 6),
14 
15     kCFRunLoopExit = (1UL << 7),
16 
17     kCFRunLoopAllActivities = 0x0FFFFFFFU
18 
19 };

   // 创建observer

    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {

        NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);

    });

 

    // 添加观察者:监听RunLoop的状态

    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

 

二 、实际应用

  1. 只在NSRUnloopDefaultModes 下显示图片

  上面再举例NSTimer中已经阐述其中原理了,在此不再重复举例了。

  2.  常驻线程

  NSThread * thread  = [NSThread   alloc ]initWithTarget  selector

  [thread start];

  通常执行完方法后线程就销毁了,那么现在有这样的需求,需要一条子线程一直存在,等待处理任务,与主线程之间互不干扰  (可以类比主线程存在原理,即添加消息循环Runloop)

1 //   通过添加port 或者timer
2 
3  [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
4 
5 [[NSRunLoop currentRunLoop] run];

 

Ps:Runloop运行首先判断Mode是否为空,如果为空则退出循环,还可以通过removePort来移除端口。本例用添加port来实现,其他方法请读者自己多尝试。:)

  3. 关于自动释放池 

  关于自动释放池,子线程开启runloop要开启针对当前线程的autoreleasepool,在每次NSRunloop休眠前清理自动释放池。

  关于自动释放池的具体用法本文暂时不进行描述,待日后在整理修改本帖。

 

 

  参考资料:https://home.cnblogs.com/blog/

       

 

浅谈k8s:k8s部署架构以及工作原理(代码片段)

对于每个想落地kubernetes应用的工程师来说,熟悉kubernetes的架构和工作原理是必经之路,也是必须知道的知识,只有了解kubernetes的架构和工作原理才能更好的应用kubernetes。kubernetes架构Kubernetes最初源于谷歌内部的Borg,提供了面... 查看详情

NSRunLoop 和 GCD 队列

】NSRunLoop和GCD队列【英文标题】:NSRunLoopandGCDqueues【发布时间】:2011-03-0200:21:30【问题描述】:我正在创建一个用于iOS的c++库(是的,不幸的是它必须是C++),它使用AVCaptureSession来捕获通过captureOutput回调传递的视频帧。C++库是... 查看详情

todo:浅谈pm2基本工作原理

TODO:浅谈pm2基本工作原理要谈Node.jspm2的工作原理,需要先来了解撒旦(Satan)和上帝(God)的关系。撒旦(Satan),主要指《圣经》中的堕天使(也称堕天使撒旦),他是反叛上帝耶和华的堕天使(FallenAngels),曾经是上帝座前... 查看详情

路由器工作原理浅谈

路由器工作原理浅谈路由器是构建整个网络最核心的设备。比较著名的品牌为cisco、锐捷、华为、TP-LINK、H3C等等。之前工作中接触的较多的是思科和银河风云,思科的主要是7609,性能十分稳定,基本上全年每天24小时工作不会出... 查看详情

NSRunLoop 用于后台任务有效

】NSRunLoop用于后台任务有效【英文标题】:NSRunLoopforbackgroundtaskvalid【发布时间】:2015-12-2206:01:01【问题描述】:我试图让我的iOS应用程序在后台永远运行,(或关于位置的位置采样和诊断)我找到了这段代码-[self.locationManagerstop... 查看详情

浅谈鸽巢原理的证明和简单应用

一、鸽巢原理的证明1.定义:若有n个鸽巢和kn+1只鸽子,所有的鸽子都进入鸽巢,那么至少有一个巢中有k+1只鸽子(n,k≥0)。2.证明(反证法):若每个鸽巢中的鸽子数都不大于k,则总鸽子数<=kn,与已知相悖。得证。3.拉姆齐(Ramsey... 查看详情

浅谈jvm及原理

 1、什么是JVM?JVM,中文名是Java虚拟机,正如它的名字,是一个虚拟机器,来模拟通用的物理机。JVM是一个标准,一套规范, 规定了.class文件在其内部运行的相关标准和规范。及其相关的内部构成。比如:所有的JVM都是基... 查看详情

浅谈jvm及原理

1、什么是JVM?JVM,中文名是Java虚拟机,正如它的名字,是一个虚拟机器,来模拟通用的物理机。JVM是一个标准,一套规范, 规定了.class文件在其内部运行的相关标准和规范。及其相关的内部构成。比如:所有的JVM都是基于栈... 查看详情

浅谈对构建之法的初步认识

 对于计算机相关专业的学生来说,我们学习了很多的专业课程,像编程语言、算法、数据结构、编译原理、软件工程等。很多学生都会有这样的疑问:我学了这么多的课程有什么用呢?在工作中有多少会真正被应用到呢?也... 查看详情

浅谈卡特兰数(catalannumber)的原理和应用

一、卡特兰数(Catalannumber)1.定义组合数学中一个常出现在各种计数问题中出现的数列(用c表示)。以比利时的数学家欧仁·查理·卡特兰的名字来命名;2.计算公式(1)递推公式c[n]=Σc[k]*c[n-k-1],边界条件为c[0]=1;其递推解为c[n]=C(2n,n)/(n+... 查看详情

NSRunLoop 正在消耗大量的 cpu 和内存

】NSRunLoop正在消耗大量的cpu和内存【英文标题】:NSRunLoopisconsumingalotofcpuandmemory【发布时间】:2012-04-2011:29:50【问题描述】:我有一个无限循环驱动我的工作线程。-(void)myThreadNSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];while(![myThread... 查看详情

javanio之浅谈内存映射文件原理与directmemory

 Java类库中的NIO包相对于IO包来说有一个新功能是内存映射文件,日常编程中并不是经常用到,但是在处理大文件时是比较理想的提高效率的手段。本文我主要想结合操作系统中(OS)相关方面的知识介绍一下原理。  ... 查看详情

浅谈c/s和b/s架构的工作原理及优缺点

 C/S架构一、C/S架构及其背景C/S架构是一种比较早的软件架构,主要应用于局域网内。在这之前经历了集中计算模式,随着计算机网络的进步与发展,尤其是可视化工具的应用,出现过两层C/S和三层C/S架构,不... 查看详情

zbar的简单使用

NSRunLoop类声明的编程接口用于管理输入源对象。一个NSRunLoop对象处理像来自窗体系统中的鼠标和键盘事件,NSPORT对象和NSConnection连接对象这类的输入源。一个NSRunLoop对象也处理的NSTimer事件。你的应用程序不能建... 查看详情

浅谈react工作原理

Reactjs起源于Facebook内部项目,是一个用来构建用户界面的javascript库,相当于MVC架构中的V层框架,与市面上其他框架不同的是,React把每一个组件当成了一个状态机,组件内部通过state来维护组件状态的变化,当组件的状态发生变... 查看详情

浅谈jvm及原理(代码片段)

1、什么是JVM?JVM,中文名是Java虚拟机,正如它的名字,是一个虚拟机器,来模拟通用的物理机。JVM是一个标准,一套规范, 规定了.class文件在其内部运行的相关标准和规范。及其相关的内部构成。比如... 查看详情

rac工作原理和相关组件

RAC工作原理和相关组件     OracleRAC是多个单实例在配置意义上的扩展,实现由两个或者多个节点(实例)使用一个共同的共享数据库(例如,一个数据库同时安装多个实例并打开)。在这种情况下,每一个单独... 查看详情

浅谈差分数组的原理及简单应用

一、差分数组的定义及用途1.定义:对于已知有n个元素的离线数列d,我们可以建立记录它每项与前一项差值的差分数组f:显然,f[1]=d[1]-0=d[1];对于整数i∈[2,n],我们让f[i]=d[i]-d[i-1]。2.简单性质:(1)计算数列各项的值:观察d[2]=f[1... 查看详情