热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入解析NSRunLoop机制

本文将详细介绍NSRunLoop的工作原理,包括其基本概念、消息类型(事件源)、运行模式、生命周期管理以及嵌套运行等关键知识点,帮助开发者更好地理解和应用这一重要技术。

在编程过程中,了解和掌握NSRunLoop的工作原理至关重要,尤其是在处理多线程和事件驱动的应用场景时。本文旨在全面解析NSRunLoop,帮助读者消除学习中的盲点。

NSRunLoop概述

NSRunLoop是一种事件处理循环,负责接收和分发事件到相应的处理器。其基本工作流程类似于一个无限循环,具体如下:

main() { initialize(); do { message = get_next_message(); process_message(message); } while (message != quit); } 

在这个过程中,NSRunLoop会等待消息(休眠),接收到消息后进行处理。NSRunLoop通过高度抽象的方式封装了这一过程,使得开发者在日常开发中几乎感觉不到其存在。

NSRunLoop可以理解为一个函数,其核心功能是消息循环。有消息时进行处理,无消息时进入休眠状态。

简单示例:创建一个新线程,添加一个定时器,并启动NSRunLoop。

- (void)timerFire { NSLog(@"mode:%@", [[NSRunLoop currentRunLoop] currentMode]); } - (void)runLoopTest { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:2 target:self selector:@selector(timerFire) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; }); } 

如果你有嵌入式操作系统开发经验,可能会对以下代码感到熟悉:

void ledTask (void *p_arg) { initialize(); while (1) { LED_ON(); delay_ms(500); LED_OFF(); delay_ms(500); } } 

这段代码实现了一个LED闪烁线程,功能简单:初始化后进入无限循环,通过延时函数使线程进入休眠状态以节省CPU资源。NSRunLoop的工作原理与此类似。

消息类型(事件源)

NSRunLoop的消息类型非常多样,主要包括:

  • Port(端口):监听程序的Mach端口,Mach端口是一种底层通信机制,用于内核与进程之间的通信。
  • Custom(自定义):由开发者自定义的消息源,通常涉及复杂的处理逻辑,苹果提供了CFRunLoopSource来辅助处理。
  • Selector Sources(选择器源):通过NSObject类的方法添加到NSRunLoop中,例如performSelector系列方法。
  • Timer Sources(定时器源):事件的发送是同步的,常用于定时任务。
  • Observers(观察者):用于观察NSRunLoop的状态变化,如进入、退出、处理定时器等。

其中,Port、Custom和Selector Sources都是异步执行的。

运行模式

NSRunLoop的运行模式使其更加灵活,适应不同的应用场景。常见的模式包括:

  • kCFRunLoopDefaultMode:默认模式,通常用于主线程。
  • UITrackingRunLoopMode:用于跟踪用户界面的触摸滑动。
  • UIInitializationRunLoopMode:App启动时的初始模式,启动完成后不再使用。
  • NSRunLoopCommonModes:包含多种模式,如默认、模态和跟踪模式。

开发者还可以自定义运行模式,通过设置不同的模式来控制事件源的响应。

生命周期管理

NSRunLoop的生命周期可以分为三个阶段:创建、运行和退出。

1. 创建

NSRunLoop是随着线程的创建而创建的,每个线程都有一个唯一的NSRunLoop实例。主线程的NSRunLoop由系统自动创建,而非主线程的NSRunLoop需要显式调用[NSRunLoop currentRunLoop]方法来创建。

2. 运行

主线程的NSRunLoop由系统自动运行,而非主线程的NSRunLoop需要显式调用run、runUntilDate或runMode:beforeDate方法来启动。

运行过程中,如果当前模式下没有事件源,NSRunLoop会直接退出。此外,某些特殊情况(如performSelector系列方法)也可能导致NSRunLoop退出。

3. 退出

NSRunLoop可以通过以下方式退出:

  • 设置最大运行时间到期。
  • 当前模式下的事件源为空。
  • 调用CFRunLoopStop方法。

需要注意的是,使用run或runUntilDate方法启动的NSRunLoop,即使调用CFRunLoopStop,也可能无法立即退出,因为这些方法会不断调用runMode:beforeDate来维持运行。

嵌套运行

NSRunLoop支持嵌套运行,即在一个runloop的回调函数中启动另一个runloop。这种机制在处理复杂的事件驱动逻辑时非常有用。

示例代码如下:

+ (void)nestTest { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSTimer *timer1 = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(timerHandle1) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer1 forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:2]]; NSLog(@"-end-"); }); } + (void)timerHandle1 { NSLog(@"timer111-%@", [[NSRunLoop currentRunLoop] currentMode]); static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSTimer *timer2 = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(timerHandle2) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer2 forMode:UITrackingRunLoopMode]; }); [[NSRunLoop currentRunLoop] runMode:UITrackingRunLoopMode beforeDate:[NSDate distantFuture]]; } + (void)timerHandle2 { NSLog(@"timer222-%@", [[NSRunLoop currentRunLoop] currentMode]); CFRunLoopStop([[NSRunLoop currentRunLoop] getCFRunLoop]); } 

在这个示例中,外层runloop运行在NSDefaultRunLoopMode模式下,内层runloop运行在UITrackingRunLoopMode模式下。通过调用CFRunLoopStop方法可以停止内层runloop,从而返回到外层runloop。

当同一模式下的runloop出现嵌套时,苹果的处理机制仍然能确保正确的执行顺序,避免了潜在的死锁问题。

注意:虽然r1和r2代表同一个runloop,但由于调用栈的不同,它们可以被视为嵌套层。理解这一点有助于更好地掌握NSRunLoop的嵌套运行机制。


推荐阅读
  • 本题探讨如何通过最大流算法解决农场排水系统的设计问题。题目要求计算从水源点到汇合点的最大水流速率,使用经典的EK(Edmonds-Karp)和Dinic算法进行求解。 ... [详细]
  • 本文深入探讨了HTTP请求和响应对象的使用,详细介绍了如何通过响应对象向客户端发送数据、处理中文乱码问题以及常见的HTTP状态码。此外,还涵盖了文件下载、请求重定向、请求转发等高级功能。 ... [详细]
  • PHP 过滤器详解
    本文深入探讨了 PHP 中的过滤器机制,包括常见的 $_SERVER 变量、filter_has_var() 函数、filter_id() 函数、filter_input() 函数及其数组形式、filter_list() 函数以及 filter_var() 和其数组形式。同时,详细介绍了各种过滤器的用途和用法。 ... [详细]
  • 本文详细探讨了HTML表单中GET和POST请求的区别,包括它们的工作原理、数据传输方式、安全性及适用场景。同时,通过实例展示了如何在Servlet中处理这两种请求。 ... [详细]
  • 丽江客栈选择问题
    本文介绍了一道经典的算法题,题目涉及在丽江河边的n家特色客栈中选择住宿方案。两位游客希望住在色调相同的两家客栈,并在晚上选择一家最低消费不超过p元的咖啡店小聚。我们将详细探讨如何计算满足条件的住宿方案总数。 ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • 本文详细介绍了如何使用 Yii2 的 GridView 组件在列表页面实现数据的直接编辑功能。通过具体的代码示例和步骤,帮助开发者快速掌握这一实用技巧。 ... [详细]
  • 使用 Azure Service Principal 和 Microsoft Graph API 获取 AAD 用户列表
    本文介绍了一段通用代码示例,该代码不仅能够操作 Azure Active Directory (AAD),还可以通过 Azure Service Principal 的授权访问和管理 Azure 订阅资源。Azure 的架构可以分为两个层级:AAD 和 Subscription。 ... [详细]
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • 本文详细介绍了 Apache Jena 库中的 Txn.executeWrite 方法,通过多个实际代码示例展示了其在不同场景下的应用,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • 本文介绍了一种从与src同级的config目录中读取属性文件内容的方法。通过使用Java的Properties类和InputStream,可以轻松加载并获取指定键对应的值。 ... [详细]
  • FinOps 与 Serverless 的结合:破解云成本难题
    本文探讨了如何通过 FinOps 实践优化 Serverless 应用的成本管理,提出了首个 Serverless 函数总成本估计模型,并分享了多种有效的成本优化策略。 ... [详细]
  • 并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
    Java并发编程实践目录并发编程01——ThreadLocal并发编程02——ConcurrentHashMap并发编程03——阻塞队列和生产者-消费者模式并发编程04——闭锁Co ... [详细]
  • 深入解析Java多线程与并发库的应用:空中网实习生面试题详解
    本文详细探讨了Java多线程与并发库的高级应用,结合空中网在挑选实习生时的面试题目,深入分析了相关技术要点和实现细节。文章通过具体的代码示例展示了如何使用Semaphore和SynchronousQueue来管理线程同步和任务调度。 ... [详细]
author-avatar
懓瑞巴蒂
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有