Blocks —— 《Objective-C高级编程 iOS与OS X多线程和内存管理》

目录

    • Blocks概要
      • 什么是Blocks
      • OC转C++方法
      • 关于几种变量的特点
    • Blocks模式
      • Block语法
      • Block类型 变量
      • 截获局部变量值
      • __block说明符
      • 截获的局部变量
    • Blocks的实现
      • Block的实质


Blocks概要

什么是Blocks

Blocks是C语言的扩充功能,即带有局部变量的匿名函数。

顾名思义,不带有名称的函数,C语言的标准可不允许存在这样的函数。就算是使用函数指针调用函数也需要知道函数名称。

OC转C++方法

因为需要看Block操作的C++源码,所以要知道转换的方法:

打开终端,cd到OC源文件.m所在的文件夹,输入clang -rewrite-objc 文件名称.m,就会在当前文件夹内自动生成对应的.cpp文件.

关于几种变量的特点

C语言函数中可能使用的变量:

  • 函数参数
  • 自动变量(局部变量)
  • 静态变量(静态局部变量)
  • 静态全局变量
  • 全局变量

而且,由于存储区域特殊,这其中有三种变量是可以在任何时候以任何状态调用的(在函数的多次调用之间能够传递值的变量):

  • 静态变量
  • 静态局部变量
  • 全局变量

虽然这些变量的作用域不同,但在整个程序中,一个变量总保持在一个内存区域。而其他两种虽然有各自相应的作用域,超过作用域后,会被销毁。

Blocks模式

Block语法

完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同:

  1. 没有函数名,即匿名函数;
  2. 带有^,因为iOS、Mac OS应用程序的源代码中将大量使用Block,所以插入该记号便于查找。

Block语法的BN范式:^ 返回值类型 参数列表 表达式。例如:

^int (int value, int count) {return count * value;}

可省略返回值类型

^(int value, int count) {return count * value;}

省略返回值类型的情况下:

  • 表达式中return的类型就是返回类型;
  • 表达式中无return语句说明是void类型;
  • 表达式中含有多个return语句时,所有return的返回值类型必须相同。

可省略参数列表,如果不使用参数:

^void (void) {printf("Blocks\n");}
//省略形式
^{printf("Blocks\n");}

Block类型 变量

定义C语言函数时,可以将所定义的函数的地址赋值给函数指针类型的变量:

int func(int count) {return count + 1;
}
int (*funcptr) (int) = &func;

同样的,Block是一种数据类型,可将Block语法赋值给声明为Block类型的变量:

//声明Block类型变量仅仅是将声明函数指针类型变量的*变为^
int (^blockName) (int);//赋值(Block内容的实现)
int (^blockName) (int) = ^(int count) {return count + 1;
};

如果我们在项目中经常使用某种相同类型的block,可以用typedef抽象出这种类型的Block:

typedef int (^AddOneBlock) (int count);
AddOneBlock blockName = ^(int count) {return count + 1;};

typedef给Block起别名,使得block的赋值和传递变得相对方便,因为block一经抽象出来了:

typedef int (^block_t) (int);//block作为参数
void func(int (^blockName) (int));
//简化后
void func(block_t blockName);//block作为返回值
int (^func() (int)) {return ^(int count) {return count + 1;};
}
//简化后
block_t func() { ... }

Block类型变量可完全像通常的C语言变量一样使用,因此也可以使用指向Block类型变量的指针,即Block指针类型变量:

typedef int (^block_t) (int);
block_t blockName = ^(int count) {return count + 1;};
block_t* blockptr = &blockName;//int result = blockName(10);
int result = (*blockptr)(10);

截获局部变量值

int a = 20;
int b = 10;void (^blockName)(void) = ^{printf("%d, %d\n", a, b);
};blockName();a++;
b++;printf("%d, %d\n", a, b);  //21, 11
blockName();  //20, 10

可以看到,使用Block时,还可以使用Block外部的局部变量。而一旦在Block内部使用了其外部变量,这些变量就会被Block保存(即被截获),从而在执行块时使用。

__block说明符

实际上,局部变量值截获只能保存执行Block语法瞬间的值,保存后就不能改写改值:

请添加图片描述

可以看到,当修改截获的局部变量值时,会产生编译错误。

若想实现在Block内部将值赋给外部的局部变量,需要在该局部变量上附加__block说明符:

__block int a = 20;
void (^blockName)(void) = ^{a = 27;printf("%d\n", a);
};
blockName();  //27
a++;
printf("%d\n", a);  //28
blockName();  //27

小结

  • 修改Block外部的局部变量,Block内部被截获的局部变量不受影响;
  • 修改Block内部的局部变量,编译不通过;
  • 附有 __block说明符的局部变量可在Block中赋值,该变量也称__block变量。

截获的局部变量

截获变量为OC对象

从前面一部分可以得知,将值赋给Block中截获的局部变量会产生编译错误。
那么截获OC对象,调用变更该对象的方法也会产生编译错误吗?

id array = [[NSMutableArray alloc] init];void (^blockName) (void) = ^{id object = [[NSObject alloc] init];[array addObject: object];
};blockName();

截获的变量值array为NSMutableArray类的对象,用C语言描述,即是截获NSMutableArray类对象用的结构体实例指针

使用截获的值,这是没有问题的,而向截获的变量array赋值则会产生编译错误:

请添加图片描述

这种情况下,需要给截获的局部变量附加__block说明符:

__block id array = [[NSMutableArray alloc] init];

截获对象为C语言数组

请添加图片描述
看似没有任何问题,只是使用了C语言的字符串字面量数组,而并没有截获的局部变量赋值。但由于在目前的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获,所以无法通过编译。

使用指针就可以解决该问题:

const char* text = "hello";void (^blockName) (void) = ^{printf("%c\n", text[2]);
};blockName();  //l

Blocks的实现

Block的实质

Block语法实际上是作为极普通的C语言源代码来处理的。
通过支持Block的编译器,含有Block语法的源代码转换为一般C语言编译器能够处理的源代码,并作为极为普通的C语言源代码被编译。

Block其实就是Objective-C对象,因为它的结构体中含有isa指针。
下面在终端通过clang将OC中Block语法转换为C++代码:clang -rewrite-objc main.m请添加图片描述

main.m

int main(void) {void (^blockName) (void) = ^{printf("Block\n");};blockName();return 0;
}

main.cpp
请添加图片描述

下面,我们将源代码分成几个部分逐步理解:

  1. 源代码中的Block语法

    //void (^blockName) (void) = ^{printf("Block\n");};
    //通过Blocks使用的匿名函数被作为简单的C语言函数来处理
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block\n");}
    

    根据Block所属的函数名(此处为main)和该Block语法在该函数出现的顺序值(此处为0)来给经clang变换的函数命名。
    该函数的参数_cself相当于C++实例方法中指向实例自身的变量this,或是OC实例方法中指向对象自身的变量self,即参数__cself为指向。

    C++的this,Objective-C的self
    定义类的实例方法:

    //C++
    void MyClass::method(int arg) {printf("%p %d", this, arg);}
    MyClass cls;
    cls.method(10);
    //OC
    - (void)method: (int)arg {printf("%p %d", self, arg);}
    MyObject* obj = [[MyObject alloc] init];
    [obj method: 10];
    

    C++、Objective-C编译器将该方法作为C语言函数来处理:

    //C++转成C
    void __ZN7MyClass6methodEi(MyClass* this, int arg) {printf("%p %d", this, arg);
    }
    struct MyClass cls;
    __ZN7MyClass6methodEi(&cls, 10);
    //OC转成C
    void _I_MyObject_method_(struct MyObject* self, SEL _cmd, int arg) {printf("%p %d", self, arg);
    }
    MyObject* obj = objc_msgSend(objc_getClass("MyObject"), sel_registerName("alloc"));
    obj = objc_msgSend(obj, sel_registerName("init"));
    objc_Send(obj, sel_registerName("method:"), 10);
    

    objc_msgSend函数根据指定的对象和函数名,从对象持有类的结构体中检索_I_MyObject_method_函数的指针并调用。
    objc_msgSend函数的第一个参数objc作为_I_MyObject_method_的第一个参数self进行传递。

  2. 来看看参数的声明:struct __main_block_impl_0* __cself,该结构体的声明如下:

    struct __main_block_impl {void* isa;int Flags;  //标志int Reserved;  //今后版本升级所需的区域void* FuncPtr;  //指针函数
    }
    struct __main_block_impl_desc_0 {unsigned long reserved;  //今后版本升级所需的区域unsigned long Block_size;  //Block大小
    }struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;//构造函数__main_block_impl_0(void* fp, struct __main_block_desc_0* desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
    }
    

    来看看构造函数的调用,因为转换较多,看起来比较复杂,以下去掉转换的部分:

    //void (*blockName) (void) = (void (*) void)&__main_block_impl_0 ((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    struct __main_block_impl_0* blockName = &tmp;
    

    该源代码将__main_block_impl_0结构体类型的局部变量,即栈上生成的__main_block_impl_0结构体实例的指针赋值给__main_block_impl_0结构体指针类型的变量blockName

    这部分代码对应的最初源代码:void (^blockName) (void) = ^{printf("Block\n");};将Block语法生成的Block赋给Block类型变量blockName,它等同于将__main_block_impl_0结构体实例的指针赋给变量blockName。

    构造函数是C++中一种特殊的成员函数,用于在创建结构体对象时对其进行初始化操作,避免对象处于未定义状态。构造函数名称必须和类(包括结构体)的名称完全相同,无返回类型(包括void),若构造函数名称和结构体名不一致,编译器将不认为这是一个有效的构造函数,而是一个普通的成员函数。

  3. 下面来分析一下该构造函数__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)中的参数
    第一个参数是由Block语法转换的C语言函数指针。第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针:

    static struct __main_block_desc_0 __main_block_desc_0_DATA = {0,sizeof(struct __main_block_impl_0)  //Block大小
    };
    
  4. 接下来来看看调用Block的部分:blockName();
    这部分源代码:

    ((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
    

    去掉转换部分:

    (*blockName->impl.FuncPtr)(blockName);
    

    可以看出这是简单的函数指针调用函数。

  5. 最后探究一下上面没有提到的_NSConcreteStackBlock

    isa = &_NSConcreteStackBlock;
    

    首先要理解OC类和对象的实质,所谓Block就是Objective-C对象。
    “id”这一变量类型用于存储OC对象,在usr/include/objc/runtime.h中是如下进行声明的:

    typedef struct objc_object {Class isa;
    }* id;typedef struct objc_class {Class isa;
    }* Class;
    

    这两种结构体归根结底是在各个对象和类的实现中使用的最基本的结构体。
    下面通过编写OC类来确认一下:

    @interface MyObject : NSObject {int val0;int val1;
    }//基于objc_object结构体,该类的对象的结构体如下:
    struct MyObject {Class isa;int val0;int val1;
    }
    

    MyObject类的实例变量val0和val1被直接声明为对象的结构体成员。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针。

    请添加图片描述

    各类的结构体就是基于objc_class结构体的class_t结构体。class_t结构体在objc4运行时库的runtime/objc-runtime-new.h中声明如下:

    struct class_t {struct class_t* isa;struct class_t* superclass;Cache cache;IMP* vtable;unitptr_t data_NEVER_USE;
    };
    

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

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

相关文章

Day18 Java学生管理系统

Day18 Java学生管理系统 一、需求分析 考虑的方面: 用户需求、功能需求、非功能性需求、约束条件、优先级和权衡、可追踪性、需求验证。 二、项目搭建 搭建学生管理系统 1、创建项目的main ;pojo ; sms ; utils包。 2、编写系统的 增(涉及到扩容–…

17.WEB渗透测试--Kali Linux(五)

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 内容参考于: 易锦网校会员专享课 上一个内容:16.WEB渗透测试--Kali Linux(四)-CSDN博客 1.ettercap简介与使用…

【技术类-04】python实现docx表格文字和段落文字的“手动换行符(软回车)”变成“段落标记(硬回车)”

作品展示: 背景需求: 把python实现docx表格文字和段落文字的“手动换行符(软回车)”变成“段落标记(硬回车)合并在一起统计数量 【技术类-02】python实现docx段落文字的“手动换行符(软回车&a…

一起玩儿3D打印机——03 Marlin固件的获取和安装环境的配置

摘要:本文介绍Marlin固件的获取和安装环境的配置 Marlin是一款开源软件,其主页为:https://marlinfw.org/,首页正中就是下载连接,如下图所示: 单击下面的“Download Marlin 2.1.2.2”按钮就会进入下载页面&a…

彻底学会系列:一、机器学习之梯度下降(1)

1 梯度下降概念 1.1 概念 梯度下降是一种优化算法,用于最小化一个函数的值,特别是用于训练机器学习模型中的参数,其基本思想是通过不断迭代调整参数的值,使得函数值沿着梯度的反方向逐渐减小,直至达到局部或全局最小…

AntV L7深圳智慧城市

本案例使用L7库和Mapbox GL JS构建深圳智慧城市。 文章目录 1. 引入 CDN 链接2. 引入组件3. 创建地图4. 创建场景5. 获取数据6. 创建面图层7. 演示效果8. 代码实现 1. 引入 CDN 链接 <!-- 1.引入CDN链接 --> <script src"https://unpkg.com/antv/l7"><…

Centos yum报错‘Connection timed out after 30002 milliseconds‘) 正在尝试其它镜像。解决办法

修改源后更新报错 我有两个Centos 一个7 一个8&#xff0c;疏忽在7上面配置了8的源后报错&#xff0c;通过下面的报错发现提示的是Centos7的源找不到&#xff0c;才意识到是不是配置错了源。 报错信息&#xff1a; http://mirrors.aliyun.com/centos/7/AppStream/x86_64/os/r…

Java 面向对象(类与对象 成员方法 方法重载 可变参数 构造方法 / 构造器 this关键字 包 访问修饰符)

目录 一、类与对象1. 类与对象的定义2. 类和对象的内存分配机制 二、成员方法1. 成员方法的定义2. 方法的调用机制3. 成员方法传参机制 三、方法重载四、可变参数1. 基本概念2. 基本语法3. 应用 五、 构造方法 / 构造器1. 特点2. 使用案例3. 对象创建的流程 六、this关键字1. 运…

【Linux】信号保存{sigset_t/sigpending/sigprocmask/bash脚本/代码演示}

文章目录 1.信号相关常见概念2.管理信号的数据结构3.初识sigset_t4.信号集操作函数4.1sigpending4.2sigprocmask4.2代码测试1.测试12.测试23.测试3 4.3bash 脚本文件 1.信号相关常见概念 信号相关动作&#xff1a;产生 发送 接收 阻塞 递达(处理) 实际执行信号的处理动作称为信…

0基础 三个月掌握C语言(11)

字符函数和字符串函数 为了方便操作字符和字符串 C语言标准库中提供了一系列库函数 接下来我们学习一下这些函数 字符分类函数 C语言提供了一系列用于字符分类的函数&#xff0c;这些函数定义在ctype.h头文件中。这些函数通常用于检查字符是否属于特定的类别&#xff0c;例如…

html--宠物

文章目录 htmljscss html <!DOCTYPE html> <html lang"en" > <head><meta charset"UTF-8"><title>CodePen - Spaceworm</title><script> window.requestAnimFrame (function() {return (window.requestAnimat…

粤嵌6818开发板触摸屏应用

一、触摸屏应用 1.触摸屏设备的名字 在Linux下&#xff0c;一切皆文件&#xff0c;触摸屏也是一个文件。 触摸屏设备的名字&#xff1a;/dev/input/event0 2.触摸屏的两个专业术语 事件 ->event0 当一些外接控制设备(鼠标、键盘&#xff0c;wifi&#xff0c;触摸屏&am…

4.1_7 文件共享

文章目录 4.1_7 文件共享&#xff08;一&#xff09;基于索引结点的共享方式&#xff08;硬链接&#xff09;&#xff08;二&#xff09;基于符号链的共享方式&#xff08;软链接&#xff09; 总结 4.1_7 文件共享 注意&#xff1a;多个用户共享同一个文件&#xff0c;意味着系…

单片机第四季-第二课:uCos2源码-BSP

1&#xff0c;初始uCos2 文件中uC开头的为uCos相关的。 2&#xff0c;uCos2源码工程建立 建立Source Insight工程 寻找main函数 (1)RTOS其实就是一个大的裸机程序&#xff0c;也是从main开始运行的 (2)main之前也是有一个汇编的启动文件的 (3)main中调用了很多初始化函数 bsp部…

《你就是孩子最好的玩具·升级版》笔记(一)尊重孩子的感受

目录 简介 经典摘录 简介 作者是&#xff08;美&#xff09;金伯莉布雷恩。奠定父母与孩子一生亲密关系的情感引导书。 什么是情感引导&#xff1f; 情感引导式教育的核心&#xff0c;就是教我们的孩子如何去合理地认知以及表达自己的感受&#xff0c;书中更侧重于在这方面为…

【NBUOJ刷题笔记】递推_递归+分治策略2

0. 前言 PS&#xff1a;本人并不是集训队的成员&#xff0c;因此代码写的烂轻点喷。。。本专题一方面是巩固自己的算法知识&#xff0c;另一方面是给NBU学弟学妹们参考解题思路&#xff08;切勿直接搬运抄袭提交作业&#xff01;&#xff01;&#xff01;&#xff09;最后&…

【matlab】如何批量修改图片命名

【matlab】如何批量修改图片命名 (●’◡’●)先赞后看养成习惯😊 假如我的图片如下,分别是1、2、3、4、5的命名 需求一:假如现在我需要在其后面统一加上_behind字符串,并且保留原命名,同时替换掉原先的图片,也就是不copy新的一份,直接在原文件夹中处理,我们可以进行…

软件杯 深度学习 python opencv 动物识别与检测

文章目录 0 前言1 深度学习实现动物识别与检测2 卷积神经网络2.1卷积层2.2 池化层2.3 激活函数2.4 全连接层2.5 使用tensorflow中keras模块实现卷积神经网络 3 YOLOV53.1 网络架构图3.2 输入端3.3 基准网络3.4 Neck网络3.5 Head输出层 4 数据集准备4.1 数据标注简介4.2 数据保存…

openEuler学习总结1(仅供学习参考)

华为的openEuler内核是源于Linux。 openEuler操作系统安装流程 第一步&#xff1a;开启虚拟化 第二步&#xff1a;安装一个虚拟化软件virtualbox 第三步&#xff1a;镜像 第四步&#xff1a;配置 设置虚拟机所在的目录 把网卡类型选择成桥接网卡 挂载镜像 设置完成&#xff0…

YOLOv9详解

1.概述 在逐层进行特征提取和空间转换的过程中&#xff0c;会损失大量信息&#xff0c;例如图中的马在建模过程中逐渐变得模糊&#xff0c;从而影响到最终的性能。YOLOv9尝试使用可编程梯度信息PGI解决这一问题。 具体来说&#xff0c; PGI包含三个部分&#xff0c;&#xff0…