[Effective Objective] 块与大中枢派发

news2025/2/25 7:13:56

为了解决多线程问题,苹果公司以全新的方式设计了多线程。核心就是“块”(block)与“大中枢派发”(Grand Central Dispatch, GCD)。

“块”是一种可在C、C++及Objective-C代码中使用的“词法闭包”,借由此机制,开发者可将代码向对象一样传递,令其在不同环境下运行。

GCD是一种与块有关的技术,它提供了对线程的抽象,这种抽象则基于“派发队列”。开发者可将块排入队列中,由GCD负责所有调度事宜。GCD会根据系统资源情况,创建、复用、摧毁后台线程,以便处理每个队列。

理解“块”这一概念

块可以实现闭包。这项语言特性是作为“扩展”而加入GCD编译器中的。

块的基础知识

块类似于直接定义在另一个函数里的函数,和定义它的函数共享一个范围内的东西。

定义简单块的例子:

^{
	//Block implementation here
}

块其实是一个值,有其相关类型。也可以把块赋给变量,然后像使用其他变量那样使用它。

例,没有参数,不返回值的块:

void (^someBlock) () = ^{
	//Block implementation here
};

这段代码定义了一个名为someBlock的变量。块类型的语法结构如下:

return_type (^block_name)(parameters)

下面这种写法所定义的块,返回int值,并且接受两个int做参数:

int (^addBlock)(int a, int b) = ^(int a, int b) {
        return a + b;
    };

使用:

int add = addBlock(2, 3);
NSLog(@"%d", add); // 5

块的强大之处是:在声明它的范围里,所有的变量都可以为其所捕获。比如,下面这段代码所定义的块,就使用了块以外的变量:

	int additional = 5;
    int (^addBlock)(int a, int b) = ^(int a, int b) {
        return a + b + additional;
    };
    
    int add = addBlock(2, 5);
    NSLog(@"%d", add); // 12

默认情况下,为块所捕获的变量,是不可以在块里修改的。如果想在块里修改变量,可以在声明变量时加上__block修饰符。

例如本例,在块内修改additional:

__block int additional = 5;
    int (^addBlock)(int a, int b) = ^(int a, int b) {
        additional++;
        return a + b + additional;
    };
    
    int add = addBlock(2, 5);
    NSLog(@"%d %d", add, additional); // 13, 6

内联块

不把块赋给局部变量,而是内联到函数调用里。这样可以把逻辑调用都放在一处。

例,用块来枚举数组中的元素,判断其中有多少个小于2的数:

NSArray* array = @[@0, @1, @2, @3, @4, @5];
    __block NSInteger count = 0;
    [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSNumber* number = (NSNumber*)obj;
        if ([number compare:@2] == NSOrderedAscending) {
            count++;
        }
    }];

块与对象类型

如果块所捕获的变量是对象类型,那么就会自动保留它。系统在释放这个块的时候也会将其一并释放。

实际上,块本身可视为对象,很多其他Objectivec-C可响应的选择子中,有很多,块也可以响应。而最重要之处在于块本身也和其他对象一样,有引用计数。当最后一个指向块的引用移走之后,块就回收了。回收时也会释放块所捕获的变量。

如果将块定义在Objective-C的实例方法中,那么除了可以访问类的所有实例变量之外,还可以使用self变量。如果通过读取或写入操作捕获了实例变量,那么也会自动把self变量一并捕获了因为实例变量是与self所指代的实例关联在一起的。

例如,下面这个块声明在EOCClass类的方法中:

@implementation EOCClass

- (void) anInstanceMethod {
    void (^someBlock)() = ^{
        _value = @"Something";
        NSLog(@"%@", _value);
    };
}

@end

如果某个EOCClass实例正在执行anInstanceMethod方法,那么self变量就指向此实例。直接访问实例变量和通过self来访问是等效的:

self->_value = @"Something";

之所以要捕获self变量,原因正在于此。我们经常通过属性访问实例变量,在这种情况下,就要指明self了:

self.value = @"Something";

块的内部结构

块本身也是对象,在存放块对象的内存区域中,首个变量是指向Class对象的指针,该指针叫做isa。其余内存里含有块对象正常运转所需的各种信息。
在这里插入图片描述

invoke

在内存布局中,最重要的就是invoke变量,这是个函数指针,指向块的实现代码。函数原型至少要接受一个void*型的参数,此参数代表块。

invoke函数需要把块对象作为参数传进来,以便于在执行块时,把捕获到的变量从内存中读出来。

descriptor

descriptor变量是指向结构体的指针,每个块里都包含此结构体,其中声明了块对象的总体大小,还声明了copy与dispose这两个辅助函数所对应的函数指针。辅助函数在拷贝及丢弃块时运行,其中会执行一些操作。

块还会把它所捕获的所有变量都拷贝一份。这些拷贝放在descriptor变量后面,捕获了多少个变量,就要占据多少内存空间。

全局块、栈块及堆块

栈块

定义块的时候,其所占的内存区域是分配在栈中的。这就是说,块只在定义它的那个范围生效。例如,下面这段代码就很危险:

void (^block)();
if (...) {
	block = ^{
		NSLog(@"Block A");
	};
} else {
	block = ^{
		NSLog(@"Block B");
	};
}
block();

定义在if及else语句中的两个块都分配在栈内存中,等离开了相应的范围,编译器很可能吧分配给栈的内存覆写掉。于是,这两个块只能保证在对应的if或else语句内有效。

堆块

未解决此问题,可给块对象发生copy消息,以拷贝。这样的话,就可以吧块从栈复制到堆了。拷贝后的块,可以在定义它的那个范围之外使用。而且一旦复制到堆上,块就成了带了引用计数的对象了。后续的复制操作都只是递增块对象的引用计数,

例:

void (^block)();
if (...) {
	block = [^{
		NSLog(@"Block A");
	} copy];
} else {
	block = [^{
		NSLog(@"Block B");
	} copy];
}
block();

现在代码就安全了。如果手动管理引用计数,那么在用完块之后还需要将其释放。

全局块

这种块不会捕捉任何状态,运行时也无须有状态来参与。块所使用的整个内存区域,在编译期已经完全确定了,因此,全局块可以声明在全局内存里,而不需要每次用的时候于栈中创建。

另外,全局块的拷贝是个空操作,因为全局块绝不可能为系统所回收。这种块实际上相当于单例。

例:

void (^block)() = &{
	NSLog(@"This is a block");
};

由于运行该块所需的全部信息都能在编译期确定,所以可把它做成全局块。

要点

  • 块是C、C++、Objective-C中的词法闭包。
  • 块可接受参数,也可返回值。
  • 块可以分配在栈或堆上,也可以是全局的。分配在栈上的块可拷贝到堆里,这样的话,就和标准的Objective-C对象一样,具备引用计数了。

为常用的块类型创建typedef

每个块都具备其“固有类型”,因而可将其赋给适当类型的变量。这个类型由块所接受的参数及其返回值组成。
例:

^(BOOL flag, int value) {
	// Implementation
	return someInt;
}

如果想要将其赋给变量,则需注意其类型。变量类型及相关赋值语句如下:

int (^variableName)(BOOL flag, int value) = ^(BOOL flag, int value) {
	// Implementation
	return someInt;
}

与其他类型的变量不同,在定义块变量时,要把变量名称放在类型之中,而不要放在右侧。这种语法非常难记,也非常难读。鉴于此,我们应该为常用的块类型其个别名。

为例隐藏复杂的块类型,需要用的C语言中名为“类型定义”的特性。typedef关键字用于给类型起个易懂的别名。

例:

typedef int (^EOCSomeBlock)(BOOL flag, int vlaue);

上面这条语句向系统中新增了一个名为EOCSomeBlock的类型。此后,创建变量直接使用新类型:

EOCSomeBlock block = ^(BOOL flag, int value) {
	// Implementation
};

块作方法参数

类里面有些方法可能需要使用块来做参数,遇到这种情况,可以通过定义别名使代码变得更为易读。

比方说,类里面又个方法,它接受一个块作为处理程序,在完成任务后执行这个块。若不定义别名,则方法签名会像下面这样:

- (void) startWithCompletHandler:(void (^)(NSData* data, NSError* error))completion;

这种情况,我们可以给参数类型起个别名,然后使用此名称来定义:

typedef void (^EOCCompletionHandler)(NSData *data, NSError *error);

- (void) startWithCompletHandler:(EOCCompletionHandler)completion;

当前,优秀的集成开发环境都可以自动吧类型定义展开,所以typedef这个功能变得很实用。

使用类型定义还有一个好处,就是当你打算重构块的类型签名时,只需要修改类型定义语句即可。

如果有好几个类都要执行相似但各有区别的异步任务,而这几个类又不能放入同一个继承体系,那么每个类就应该有自己的completion handler类型。这几个completion handler的签名也许完全相同,但最好还是在每个类里都各自定义一个别名。若这些类能纳入同一个继承中,则应该将类型定义语句放在超类中,以供个字类使用。

要点

  • 以typedef重新定义块类型,可令块变量用起来更加简单。
  • 定义新类型时应遵从现有的命名习惯,勿使其名称与别的类型相冲突。
  • 不妨为同一个块签名定义多个类型别名。如果要重构的代码使用了块类型的某个别名,那么只需修改相应typedef中的块签名即可,无须改动其他typedef。

用handler块降低代码分散程度

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/197753.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在一起多少天怎么设置?如何微信推送在一起多少天

马上情人节要到了,你和你的对象在一起多久了?两个人在恋爱中,会需要记录彼此在一起的每一天,特别是一些重要的纪念日比如100天纪念日,365天、或者520天纪念日。市面上有许多工具,可以帮我们记录这些重要的日…

指针空值nullptr(C++11)

在良好的C/C编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现 不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下 方式对其进行初始化:void TestPtr() { in…

【Docker 02】docker镜像和容器命令大全

对于入门学习者,更推荐的方式是通过官网的Reffrence手册,学习使用命令,不仅存在用法,选项参数的解释,还有用力example。 docker命令的基本语法结构: docker 子命令 [选项] [参数] 一、Docker基本命令 1.镜像有关 一批模板文件,不同的镜像可以包含的环境内容是不一样的,…

深入了解多线程原理

目录 背景知识: 什么是进程? 什么是线程? 线程与进程的区别: Thread类及常用方法: 循环打印的例子: start() 和 run() 的区别: 通过监视窗口查看线程: 创建线程: 1.继承 …

console控制台有sql语句输出但log文件中不输出sql解决方式

控制台可以输出sql,但是log文件中无sql输出,如何解决?把握两点就可以输出:第一点,mybatis 本身的logImpl配置这个参数是配置mybatis所使用的日志框架,取值范围如下:SLF4JLOG4J #表示使用LOG4J作…

提名倒计时! | 2022 龙蜥社区优秀贡献者

各位盆友们:2022 年,那些为龙蜥壮大做出杰出贡献的人们,包括开源背后的推动者、组织者、布道者、代码贡献者,让我们看到了热爱技术的力量!为此社区推出「2022 龙蜥社区优秀贡献者」活动。截至目前,距离报名…

CSAPP Malloc Lab

CSAPP Malloc Lab 在这个实验室中,您将为C程序编写一个动态存储分配器,即您自己版本的malloc、free和realloc例程,实现一个正确,高效和快速的分配器。本实验性能指标有两个方面,内存利用率和吞吐量,这两个…

fpga图像处理(基于camera的图像读取和显示)

【声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 市面上目前很多的fpga开发板都有camera到lcd的显示demo。处理流程也是很相似的。一般的流程都是fpga首先初始化cmos,接着就是把数据从cmos读出来存储到sdram里面,显示模块再从sdra…

C语言高级教程-C语言数组(六):变长数组

C语言高级教程-C语言数组(六):变长数组一、本文的编译环境二、一维数组在执行期间确定长度三、二维数组在执行期间确定长度四、一维变长数组实例五、完整程序5.1 Main.h 文件程序5.2 Main.c 文件程序六、总结一、本文的编译环境 本文的编译环…

压缩包版本快速安装MySQL教程

安装MySQL 跟随老师 狂神学java 学习地址 bilibilihttps://www.bilibili.com/video/BV1NJ411J79W?p1&vd_source69de4cea8c2ffc0f520876695f09a2da 这里建议大家使用压缩版 , 安装快 , 方便 . 不复杂 . 1、软件下载mysql5.7 64位下载地址: https://dev.mysql.com/get/Dow…

数据治理与IT治理的关系

前面我们辨析了数据治理的概念。这一篇文章要讲数据治理与IT治理的关系,首先来看看IT治理的概念。IT治理的理念最早是IBM(InternationalBusiness Machines Corporation,国际商业机器公司)引入中国的,属于公司治理的一部…

中金公司:全面注册制监管规则解读(附97页报告原文pdf下载链接)

省时查报告-专业、及时、全面的行研报告库省时查方案-专业、及时、全面的营销策划方案库【免费下载】2022年12月份热门报告盘点罗振宇2023年跨年演讲PPT原稿吴晓波2022年年终秀演讲PPT原稿推荐技术在vivo互联网商业化业务中的实践.pdf2023年,如何科学制定年度规划&a…

Spring Batch 批处理数据表

目录 引言 概述 batch_job_instance表 batch_job_execution表 batch_job_execution_context表 batch_job_execution_params表 btch_step_execution表 batch_step_execution_context表 H2内存数据库 转视频版 引言 接着上篇:Spring Batch 步骤对象-返回状…

MybatisPlus多表查询之零sql编写实现

1.前言 年初节奏还没有快起来,适合做做技术前瞻,无论是对个人还是团队都是好事。真要说分享,其实感觉也没啥好分享的,就像接手新项目一样,代码都是自己看,别人讲的再多,不看,不用&am…

OpenMP For Construct dynamic 调度方式实现原理和源码分析

OpenMP For Construct dynamic 调度方式实现原理和源码分析 前言 在本篇文章当中主要给大家介绍 OpenMp for construct 的实现原理,以及与他相关的动态库函数分析,与 for construct 非常相关的是循环的调度方式,在 OpenMP 当中一共有四种调…

KVM,QEMU与libvirt关系

KVM:负责cpu虚拟化内存虚拟化,实现了cpu和内存的虚拟化,但kvm不能模拟其他设备;KVM是linux内核的模块,它需要CPU的支持,采用硬件辅助虚拟化技术Intel-VT,AMD-V,内存的相关如Intel的E…

FPGA纯verilog代码实现8位精简指令集CPU,一学期的微机原理不如看懂这套代码,提供工程源码和技术支持

目录1、前言2、设计思想和架构3、硬件组成讲解4、vivado仿真5、vivado工程6、上板调试验证7、福利:工程源码获取1、前言 本文章主要针对大学本科阶段学生; 读文章之前先来几个灵魂拷问: 1、你是否学过《微机原理》、《单片机》、《汇编语言》…

怎样做一个优秀的程序员?这10个问题ChatGPT这样说 ……

本文目录 1 怎样做一个优秀的程序员? 2 怎样成为优秀的架构师? 3 怎样写容易阅读的代码? 4 怎样做项目管理? 5 怎样学习计算机程序设计? 6 怎样提升个人影响力? 7 怎样提升认知? 8 程序员怎样面试通过几率高? 9 怎样提升研发效能? 10 怎样保障软件系统的稳定…

字体图标的使用【购物车】

方法1 <link rel"stylesheet" href"2购物车/iconfont.css"><style>*{padding: 0;margin: 0;}li{width: 90px;height: 40px;background-color: pink;margin: 0 auto; list-style: none;text-align: center;line-height: 40px;}a{text-decoratio…

4.6 Python元组

列表非常适合用于存储在程序运行期间可能变化的数据集。Python将不能修改的值称为不可变的&#xff0c;而不可变的列表被称为元组。4.5.1 定义元组元组看起来犹如列表&#xff0c;但使用圆括号而不是方括号来标识。定义元组后&#xff0c;就可以使用索引来访问其元素&#xff0…