GCD常用函数拾遗

2024-09-06 00:38
文章标签 函数 常用 拾遗 gcd

本文主要是介绍GCD常用函数拾遗,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • dispatch_block_t
  • 监听block执行结束
    • dispatch_block_wait
    • dispatch_block_notify
  • 撤销block
  • 总结
  • 参考

这几天偶尔又回顾了下GCD的知识。之前我一直以为自己对于GCD已经大体有个整体掌握了,却发现仍还有一些知识点的遗漏。于是写在这里,算是对之前GCD常用函数文章的补充。

dispatch_block_t

在GCD中,所有的任务,都是封装成dispatch block的形式进行派发的。比如

void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

dispatch_async的第二个参数,就是一个的dispatch block。

在Xcode中,查看dispatch_block_t的定义:

typedef void (^dispatch_block_t)(void);

看上去仅是一个无参无返回的普通block而已。

而在dispatch_block_t的定义上有一段注释,很有意思:

@typedef dispatch_block_t
@abstract
The type of blocks submitted to dispatch queues, which take no arguments
and have no return value.
@discussion
When not building with Objective-C ARC, a block object allocated on or
copied to the heap must be released with a -[release] message or the
Block_release() function.
The declaration of a block literal allocates storage on the stack.
Therefore, this is an invalid construct:

dispatch_block_t block;
if (x) {
block = ^{ printf(“true\n”); };
} else {
block = ^{ printf(“false\n”); };
}
block(); // unsafe!!!

What is happening behind the scenes:

if (x) {
struct Block __tmp_1 = …; // setup details
block = &__tmp_1;
} else {
struct Block __tmp_2 = …; // setup details
block = &__tmp_2;
}

As the example demonstrates, the address of a stack variable is escaping the
scope in which it is allocated. That is a classic C bug.
Instead, the block literal must be copied to the heap with the Block_copy()
function or by sending it a -[copy] message.

上面说了一堆,重点就一句话:

The declaration of a block literal allocates storage on the stack.

声明的block变量是在栈上申请的内存空间。(这里是对所有的block来说的,不仅仅是指dispatch block)

既然是栈上的空间,我们就要留心栈空间自动回收的问题:

 dispatch_block_t block;if (x) {block = ^{ printf("true\n"); };} else {block = ^{ printf("false\n"); };}block(); // unsafe!!!

这种操作是不可以的,因为当最后调用block()的时候,括号里面的block栈变量可能已经被回收掉了。

在GCD中创建dispatch block,有以下两种形式:

dispatch_block_t dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block);dispatch_block_t dispatch_block_create_with_qos_class(dispatch_block_flags_t flags,dispatch_qos_class_t qos_class, int relative_priority,dispatch_block_t block);

通过这两种方式创建的dispatch block变量,是在堆上分配的内存。

这两个函数第一个参数都是dispatch_block_flags_t类型,他是一个枚举类型:

FlagsExplanation
DISPATCH_BLOCK_ASSIGN_CURRENTSet the attributes of the work item to match the attributes of the current execution context.
DISPATCH_BLOCK_BARRIERCause the work item to act as a barrier block when submitted to a concurrent queue.
DISPATCH_BLOCK_DETACHEDDisassociate the work item’s attributes from the current execution context.
DISPATCH_BLOCK_ENFORCE_QOS_CLASSPrefer the quality-of-service class associated with the block.
DISPATCH_BLOCK_INHERIT_QOS_CLASSPrefer the quality-of-service class associated with the current execution context.
DISPATCH_BLOCK_NO_QOS_CLASSExecute the work item without assigning a quality-of-service class.

而对于dispatch_block_create_with_qos_class方法,多了两个参数dispatch_qos_class_t qos_class,int relative_priority,分别用来设置任务的QoS(Quality of Service)class和QoS的相对优先级的。

QoSExplanation
QOS_CLASS_USER_INTERACTIVE:user interactive等级表示任务需要被立即执行,用来在响应事件之后更新 UI,来提供好的用户体验。这个等级最好保持小规模。
QOS_CLASS_USER_INITIATED:user initiated等级表示任务由 UI 发起异步执行。适用场景是需要及时结果同时又可以继续交互的时候。
QOS_CLASS_DEFAULT:default默认优先级
QOS_CLASS_UTILITY:utility等级表示需要长时间运行的任务,伴有用户可见进度指示器。经常会用来做计算,I/O,网络,持续的数据填充等任务。这个任务节能。
QOS_CLASS_BACKGROUND:background等级表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务。
QOS_CLASS_UNSPECIFIED:unspecified未指明

relative_priority是一个小于等于0,大于等于QOS_MIN_RELATIVE_PRIORITY的数值。表示与最大的优先级的负偏移度。

例子:

dispatch_queue_t concurrentQuene = dispatch_queue_create("concurrentQuene", DISPATCH_QUEUE_CONCURRENT);dispatch_block_t block = dispatch_block_create(0, ^{NSLog(@"normal do some thing...");
});
dispatch_async(concurrentQuene, block);//
dispatch_block_t qosBlock = dispatch_block_create_with_qos_class(0, QOS_CLASS_DEFAULT, 0, ^{NSLog(@"qos do some thing...");
});
dispatch_async(concurrentQuene, qosBlock);

通过上面dispatch_block_createdispatch_block_create_with_qos_class可以创建拥有flag或优先级的dispatch_block_t对象。然后把他放到queue中,就可以执行了。

注意,dispatch_block_t的定义仅仅是一个无参无返回的简单block:

typedef void (^dispatch_block_t)(void);

那么问题来了,创建的dispatch block对象,又是在哪里存放其优先级以及flag信息的呢?

其实,dispatch_block_t这个东西,并不像它表面这么简单。而这就说来话长了,我们会另起一篇来单独介绍。

监听block执行结束

当我们将dispatch block放到queue中执行后,有时候需要知道block的执行结束时机。

dispatch_block_wait

intptr_t dispatch_block_wait(dispatch_block_t block, dispatch_time_t timeout);

dispatch_block_wait会阻塞当前线程来等待block的结束,或直到timeout。其返回值为0表示wait函数是执行完毕block退出,非0则表示超时退出。

timeout可以选择两个特殊的值,DISPATCH_TIME_NOWDISPATCH_TIME_FOREVER ,表示不等待或一直等待。

通过这个函数,我们不能够同时等待多个block的结束。如果有这个需求,我们可以使用dispath_group_wait方法。

这个方法不是线程安全的,而且,当block被提交的queue之后,就被视作是执行,即使调用dispatch_block_cancel方法cancel, 也被视作是执行了。

示例

dispatch_queue_t concurrentQuene = dispatch_queue_create("concurrentQuene", DISPATCH_QUEUE_CONCURRENT);dispatch_async(concurrentQuene, ^{dispatch_queue_t allTasksQueue = dispatch_queue_create("allTasksQueue", DISPATCH_QUEUE_CONCURRENT);dispatch_block_t block = dispatch_block_create(0, ^{NSLog(@"开始执行");[NSThread sleepForTimeInterval:3];NSLog(@"结束执行");});dispatch_async(allTasksQueue, block);// 等待时长,10s 之后超时dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC));long resutl = dispatch_block_wait(block, timeout);if (resutl == 0) {NSLog(@"执行成功");} else {NSLog(@"执行超时");}
});

dispatch_block_notify

如果不想阻塞线程来等待block的结束,则可以用dispatch_block_notify函数。

void dispatch_block_notify(dispatch_block_t block,     dispatch_queue_t queue, dispatch_block_t notification_block);

该函数接收三个参数,第一个参数是需要监视的 block,第二个参数是监听的 block 执行结束之后要提交执行的队列 queue,第三个参数是待加入到队列中的 block。 和 dispatch_block_wait 的不同之处在于:dispatch_block_notify 函数不会阻塞当前线程。
例如:

NSLog(@"---- 开始设置任务 ----");
dispatch_queue_t serialQueue =   dispatch_queue_create("com.fyf.serialqueue",   DISPATCH_QUEUE_SERIAL);// 耗时任务
dispatch_block_t taskBlock = dispatch_block_create(0, ^{NSLog(@"开始耗时任务");[NSThread sleepForTimeInterval:2.f];NSLog(@"完成耗时任务");
});dispatch_async(serialQueue, taskBlock);// 更新 UI
dispatch_block_t refreshUI = dispatch_block_create(0, ^{NSLog(@"更新 UI");
});// 设置监听
dispatch_block_notify(taskBlock, dispatch_get_main_queue(), refreshUI);
NSLog(@"---- 完成设置任务 ----");

注意,无论是dispatch_block_wait还是dispatch_block_notify函数,其要等待的block必须是有dispatch_block_createdispatch_block_create_with_qos_class方法创建的,否则,其行为是未定义的。

撤销block

当我们把block送到queue中之后,GCD会自动安排block的执行。如果我们想撤销掉block的执行,可以调用函数:

void dispatch_block_cancel(dispatch_block_t block);

这个函数用异步的方式取消指定的 block。取消操作使将来执行 dispatch block 立即返回,但是对已经在执行的 dispatch block 没有任何影响。当一个 block 被取消时,它会立即释放捕获的资源。如果要在一个 block 中对某些对象进行释放操作,在取消这个 block 的时候,需要确保内存不会泄漏。

示例

dispatch_queue_t serialQueue = dispatch_queue_create("com.fyf.serialqueue", DISPATCH_QUEUE_SERIAL);// 耗时任务
dispatch_block_t firstTaskBlock = dispatch_block_create(0, ^{NSLog(@"开始第一个任务");[NSThread sleepForTimeInterval:1.5f];NSLog(@"结束第一个任务");
});// 耗时任务
dispatch_block_t secTaskBlock = dispatch_block_create(0, ^{NSLog(@"开始第二个任务");[NSThread sleepForTimeInterval:2.f];NSLog(@"结束第二个任务");
});dispatch_async(serialQueue, firstTaskBlock);
dispatch_async(serialQueue, secTaskBlock);// 等待 1s,让第一个任务开始运行
[NSThread sleepForTimeInterval:1];dispatch_block_cancel(firstTaskBlock);
NSLog(@"尝试过取消第一个任务");dispatch_block_cancel(secTaskBlock);
NSLog(@"尝试过取消第二个任务");

输出

开始第一个任务
尝试过取消第一个任务
尝试过取消第二个任务
结束第一个任务

dispatch_block_cancel 对已经在执行的任务不起作用,只能取消尚未执行的任务。

总结

上面讲到的几个函数dispatch_block_create, dispatch_block_create_with_qos_class, dispatch_block_wait, dispatch_block_notify, dispatch_block_cancle,都是在iOS 8+引进的。因此,如果要调用这些函数,其参数block,必须是又create block方法创建的,否则会引起未知的错误。

而对于dispatch_group_notify, dispatch_sync, dispatch_async等方法,是在iOS 4+就有的,这些函数的block参数,是没有限制必须使用create block方法创建的。

在使用这些函数的时候,需要注意一下。

参考

本文大部分的内容,均取自:
GCD 之任务操作(Dispatch Block)

这篇关于GCD常用函数拾遗的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1140507

相关文章

Kotlin 作用域函数apply、let、run、with、also使用指南

《Kotlin作用域函数apply、let、run、with、also使用指南》在Kotlin开发中,作用域函数(ScopeFunctions)是一组能让代码更简洁、更函数式的高阶函数,本文将... 目录一、引言:为什么需要作用域函数?二、作用域函China编程数详解1. apply:对象配置的 “流式构建器”最

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

Linux上设置Ollama服务配置(常用环境变量)

《Linux上设置Ollama服务配置(常用环境变量)》本文主要介绍了Linux上设置Ollama服务配置(常用环境变量),Ollama提供了多种环境变量供配置,如调试模式、模型目录等,下面就来介绍一... 目录在 linux 上设置环境变量配置 OllamPOgxSRJfa手动安装安装特定版本查看日志在

Java常用注解扩展对比举例详解

《Java常用注解扩展对比举例详解》:本文主要介绍Java常用注解扩展对比的相关资料,提供了丰富的代码示例,并总结了最佳实践建议,帮助开发者更好地理解和应用这些注解,需要的朋友可以参考下... 目录一、@Controller 与 @RestController 对比二、使用 @Data 与 不使用 @Dat

Mysql中深分页的五种常用方法整理

《Mysql中深分页的五种常用方法整理》在数据量非常大的情况下,深分页查询则变得很常见,这篇文章为大家整理了5个常用的方法,文中的示例代码讲解详细,大家可以根据自己的需求进行选择... 目录方案一:延迟关联 (Deferred Join)方案二:有序唯一键分页 (Cursor-based Paginatio

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

Python实现常用文本内容提取

《Python实现常用文本内容提取》在日常工作和学习中,我们经常需要从PDF、Word文档中提取文本,本文将介绍如何使用Python编写一个文本内容提取工具,有需要的小伙伴可以参考下... 目录一、引言二、文本内容提取的原理三、文本内容提取的设计四、文本内容提取的实现五、完整代码示例一、引言在日常工作和学

Redis中的常用的五种数据类型详解

《Redis中的常用的五种数据类型详解》:本文主要介绍Redis中的常用的五种数据类型详解,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Redis常用的五种数据类型一、字符串(String)简介常用命令应用场景二、哈希(Hash)简介常用命令应用场景三、列表(L

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

kotlin的函数forEach示例详解

《kotlin的函数forEach示例详解》在Kotlin中,forEach是一个高阶函数,用于遍历集合中的每个元素并对其执行指定的操作,它的核心特点是简洁、函数式,适用于需要遍历集合且无需返回值的场... 目录一、基本用法1️⃣ 遍历集合2️⃣ 遍历数组3️⃣ 遍历 Map二、与 for 循环的区别三、高