[iOS]浅谈NSRunloop工作原理和相关应用

时间:2022-09-05 14:37:42

一. 认识NSRunloop

 1.1 NSRunloop与程序运行

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

 #import <UIKit/UIKit.h>

 #import "AppDelegate.h"

 int main(int argc, char * argv[]) {

     @autoreleasepool {

         return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

     }

 }

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

 int main(int argc, char * argv[]) {

   BOOL runnning =YES;
do{
...
//处理各种操作 各种事件
...
}while(running); return ;
}

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

      [iOS]浅谈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是线程。

 // should only be called by Foundation
// t==0 is a synonym for "main thread" that always works //函数返回值为CFRunLoopRef 形参类型为pthread_t 根据线程创建runloop对象 CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { if (pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } __CFLock(&loopsLock); if (!__CFRunLoops) { __CFUnlock(&loopsLock); CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, , NULL, &kCFTypeDictionaryValueCallBacks); //由此句可得出 调用其他线程NSRunloop对象也会首先创建主线程NSRunloop对象
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFLock(&loopsLock); } CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); if (!loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (!loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFUnlock(&loopsLock); CFRelease(newLoop); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if ( == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-), (void (*)(void *))__CFFinalizeRunLoop); }
}
return loop;
}

   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。

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

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

  GCD定时器

  GCD定时器的优点有很多,首先不受Mode的影响,而NSTimer受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状态改变

 

 /* Run Loop Observer Activities */

 typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {

     kCFRunLoopEntry = (1UL << ),

     kCFRunLoopBeforeTimers = (1UL << ),

     kCFRunLoopBeforeSources = (1UL << ),

     kCFRunLoopBeforeWaiting = (1UL << ),

     kCFRunLoopAfterWaiting = (1UL << ),

     kCFRunLoopExit = (1UL << ),

     kCFRunLoopAllActivities = 0x0FFFFFFFU

 };

   // 创建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)

 //   通过添加port 或者timer

  [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];

 [[NSRunLoop currentRunLoop] run];

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

  3. 关于自动释放池 

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

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

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

       

[iOS]浅谈NSRunloop工作原理和相关应用的更多相关文章

  1. 浅谈React工作原理

    浅谈React工作原理:https://www.cnblogs.com/yikuu/p/9660932.html 转自:https://cloud.tencent.com/info/63f656e0b ...

  2. 【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之RAC 工作原理和相关组件(三)

    RAC 工作原理和相关组件(三) 概述:写下本文档的初衷和动力,来源于上篇的<oracle基本操作手册>.oracle基本操作手册是作者研一假期对oracle基础知识学习的汇总.然后形成体 ...

  3. 转载&colon;【Oracle 集群】RAC知识图文详细教程&lpar;三&rpar;--RAC工作原理和相关组件

    文章导航 集群概念介绍(一) ORACLE集群概念和原理(二) RAC 工作原理和相关组件(三) 缓存融合技术(四) RAC 特殊问题和实战经验(五) ORACLE 11 G版本2 RAC在LINUX ...

  4. 浅谈html运行原理

    浅谈HTML运行原理,所谓的HTML简单的来说就是一个网页,虽然第一节就讲html原理可能大家会听不懂,就当是给一个初步印象把,至少大概知道一个网页的运行流程是怎样的,下面上一张图: 大致的一个htm ...

  5. 【转】【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之RAC 工作原理和相关组件(三)

    原文地址:http://www.cnblogs.com/baiboy/p/orc3.html 阅读目录 目录 RAC 工作原理和相关组件 ClusterWare 架构 RAC 软件结构 集群注册(OC ...

  6. 浅谈C&plus;&plus;编译原理 ------ C&plus;&plus;编译器与链接器工作原理

    原文:https://blog.csdn.net/zyh821351004/article/details/46425823 第一篇:      首先是预编译,这一步可以粗略的认为只做了一件事情,那就 ...

  7. IOS 浅谈闭包block的使用

    前言:对于ios初学者,block通常用于逆向传值,遍历等,会使用,但是可能心虚,会感觉block很神秘,那么下面就一起来揭开它的面纱吧. ps: 下面重点讲叙了闭包的概念,常用的语法,以及访问变量, ...

  8. 10分钟浅谈CSRF突破原理,Web安全的第一防线!

    CSRF攻击即跨站请求伪造(跨站点请求伪造),是一种对网站的恶意利用,听起来似乎与XSS跨站脚本攻击有点相似,但实际上彼此相差很大,XSS利用的是站点内的信任用户,而CSRF则是通过伪装来自受信任用户 ...

  9. ios浅谈关于nil和 NIL区别及相关问题(转)

    转自:http://blog.csdn.net/guozh/article/details/8469131 个就是将引用技术减1,所谓的引用计数就是看看有多个指针指向一块内存实体,当release一次 ...

随机推荐

  1. 在WPF中使用依赖注入的方式创建视图

    在WPF中使用依赖注入的方式创建视图 0x00 问题的产生 互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少.我之前主要做W ...

  2. 简单理解前端web开发的MVC模式

    随着前端Ajax兴起.前端开发工作进一步划分:js开发和ui页面制作.另外从整个前端项目的清晰明朗以可扩展性角度来看,MVC的应用也越来越必要,特别是对大的项目. 例如 需要给一个页面上的button ...

  3. &lbrack;转&rsqb;ef获取某个表中的部分字段值

    我有个新闻表 id,title,body,createtime,author,click 使用ef4.1 仅仅读取 id,title,createtime 并显示在页面上. public static ...

  4. The type R is already defined 错误解决办法

    今天在导入一个开源项目的时候遇到了The type R is already defined的错误,试过了删除R,clear project都还是报这个错,Google一下之后找到了解决办法在 Pro ...

  5. VS 2013--工程的创建,scanf报错,常用快捷键,行号设置

    一.创建一个工程(这里是C++,其他的一样的) 在vs页面上点击 文件-->新建-->项目: 会出现如下界面,自己改名字和存贮位置就可以了 确定,然后点击下一步: 这样就建好了一个工程,然 ...

  6. Nginx学习系列一搭建环境

    1.Win10下安装vmware14虚拟机软件 官方下载地址 全程next,输入key,激活即可. 2.在虚拟机中安装Linux服务器环境,操作系统为Centos7 继续下一步,安装完成! 3.下载C ...

  7. Spring Boot 2&period;x (十五):Dubbo &plus; Zookeeper &plus; 新版Dubbo Admin

    Dubbo 简介 Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的RPC实现服务的输出和输入功能,可以和Spring框架无缝集成. 它提供了三大核心能力: 面向接口的远程 ...

  8. 最近面试 Java 后端的感受!

    来源:cnblogs.com/JavaArchitect/p/10011253.html 上周,密集面试了若干位Java后端候选人,工作经验在3到5年间.我的标准其实不复杂: 第一能干活,第二Java ...

  9. golang 与 c语言 之间传递指针的规则提案

    https://go.googlesource.com/proposal/+/master/design/12416-cgo-pointers.md https://github.com/golang ...

  10. java项目显示红叉,程序却没有错误

    转 http://blog.sina.com.cn/s/blog_825b7d7c0102w7rq.html (2016-07-02 11:38:38)   分类: javaWeb 电脑换了不同版本的 ...