先看下面的一段代码,判断输出结果:

int a = 0;
void (^block)(void) = ^{printf("a = %i\n",a);};
a=10;
block();

经过运行之后我们发现,输出结果为“a = 0”,而不是"a = 10"。

如果感觉答案是“a = 10”的同学看下面的代码或许会有所感觉,明显会报错,提示“Use of undeclared identifier 'a' ”。

void (^block)(void) = ^(){printf("a = %i\n",a);};
int a=10;
block();

我们发现block块在初始化的时候就要获取局部变量(局部变量)a的值,之后a的值的变化并不会影响block的运行结果。

*所说的局部变量为block外的局部变量,而非block内定义的。

Block结构:

到底block是如何实现的呢?下面是一个最简单的block的定义与使用

int main(int argc, const char * argv[]) {void (^block)(void) = ^{printf("This is a block!");};block();
}

通过终端,我们找到main.m文件所在路径,然后输入

clang -rewrite-objc main.m

(通过clang编译器,我们可以获得在编译过程中生产的中间代码,看block时如何实现的)

在当前目录下找到main.cpp文件,打开后能看到block生成了很多结构体如下:(简化后代码,可查找关键字),

struct __block_impl {void *isa;//block存放位置,取值为_NSConcretGlobalBlock(全局区)、_NSConcretStackBlock(栈区)、_NSConcretMallocBlock(堆区)int Flags;//用于按bit位表示一些block的附加信息,block copy的实现代码可以看到对该变量的使用。int Reserved;//保留变量。void *FuncPtr;
};static struct __main_block_desc_0 {size_t reserved;size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("This is a 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;}
};int main(int argc, const char * argv[]) {void (*block)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

看上去很多,其实没那么难理解,一个一个来分别介绍他们的意思:

__block_impl:block的一些基础属性,像是block的基类。

__main_block_desc_0:block的描述,他有一个实例__main_block_desc_0_DATA

__main_block_impl_0:block变量(包含上面两个结构体对象)。

__main_block_func_0:block的匿名函数。存放block内的语句。

命名规则:“main”为block所在函数名,如果定义在函数外为block的名字,末尾的“0”表示为当前函数中第“0”个block。

在末尾的main函数中,我们可以看到block的初始化是调用__main_block_impl_0的构造函数,所以block的内容简化后为:

struct __main_block_impl_0 {void *isa;int Flags;int Reserved;void *FuncPtr;size_t reserved;size_t Block_size;__main_block_impl_0(#block匿名函数的指针#,#__main_block_desc_0的实例指针#, int flags=0);
};

由此可见:block其实就是一个Object-c对象。

局部变量:

通过clang -rewrite-objc 指令编译本文开头的那段代码我们找出与之前的编译结果的不同:

struct __main_block_impl_0 {struct __block_impl impl;       struct __main_block_desc_0* Desc;int a; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
//block的匿名函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself){int a = __cself->a; // bound by copyprintf("a = %i\n",a);
}
//主方法
int main(int argc, const char * argv[]) {int a = 0;//block初始化void (*block)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);a=10;//block调用((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}</span>

比较后发现:

__main_block_impl_0的属性中多了一个变量a;

__main_block_impl_0初始化方法的参数中也有接收变量对a赋值的方法。

__main_block_func_0而在匿名函数中多了一行“int a = __cself->a;”

局部变量的传递的实现方法,当block在初始化的时候,通过构造函数把a的值通过值传递传入block,而在__main_block_func_0方法中,通过int a创建变量来接收。最后在匿名函数中使用时又创建临时变量类获得a。所以在block执行时,输出的a为block初始化时获得的a的值,使用的是a的副本。

这也能说明为什么,block中不让修该局部变量。首先在block中修改局部变量的值本是没问题的,但是修改的只是和局部变量名字一样的副本变量的值,这并不会影响局部变量本来的值。为了不让开发者迷惑,所以就不让赋值。

那么如果获取的局部变量是OC对象呢

int main(int argc, const char * argv[]) {NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:0];void (^block)(void) = ^{[array addObject:@"block"];};block();
}

这么写是能运行的,因为array是一个指针,我们并没有改变指针的值。

static修饰符与全局变量

全局变量或静态变量都在全局区,在内存中的地址是固定的,Block在初始化时读取的是该变量值地址,当调用的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量。而且可以在block中修改他们的值。

int a=0;
int main(int argc, const char * argv[]) {static int b=0;void (^block)(void) = ^{NSLog(@"a=%i,b=%i",++a,++b);};a = 10;b = 10;block();
}

运行的结果为(不是“a=1,b=1”):

a=11,b=11

__block修饰符

之前了解了block中用到局部变量时,使用的是局部变量的副本,当我们要想局部变量能在block中进行修改时,我们通常会在局部变量前写上__block修饰符,那么它又是如何实现的呢。对如下代码进行编译:

int main(int argc, const char * argv[]) {__block int a=0;void (^block)(void) = ^{printf("a = %i",++a);};block();
}

编译后的代码简化为(只列出有变化的部分)

struct __Block_byref_a_0 {void *__isa;__Block_byref_a_0 *__forwarding;int __flags;int __size;int a;
};struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_a_0 *a; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_a_0 *a = __cself->a; // bound by refprintf("a = %i",++(a->__forwarding->a));}static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}int main(int argc, const char * argv[]) {__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};void (*block)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

我们发现多了很多东西,看上去很乱,我们一个一个来,首先我们看主函数main里第一行,由__block int a=0;编译而来。当我们为局部变量加上__block修饰符时,编译器会自动创建一个关于这个变量的结构体__Block_byref_a_0,而变量的初始化,变为结构体的初始化,将局部变量对象化了。

在关于变量a的结构体__Block_byref_a_0中,有“int a;”用于存放变量值,有“__Block_byref_a_0 *__forwarding;”一个关于自己的指针,以及其他一些属性。而block初始化时,将对象化后的a作为了构造函数的参数。由之前分析我们已经得知如果block获取的局部变量是OC对象,是可以对它进行操作的。

   为什么不直接创建变量a的指针

到这里应该理解了__block的实际工作,但是肯定会有人疑惑,说到底,将a对象化的目的只是因为对象传递得时候传的是指针,那么为什么不直接创建一个指针指向变量a,而要将他对象化。说到这里就要先说说内存中栈和堆,函数内局部变量的初始化是分配到栈中,而对象的初始化是分配在堆中(ios中alloc都是存放在堆中),变量a是分配在栈中,如果创建一栈变量的指针很危险,栈相对于堆而言是编译器自动释放,当函数结束时,栈就会被释放,而block对象通常分配在堆上,还没有被释放,而这时候原来的指针就会变成野指针,对block而言是不安全的,

block的内存分配
  初始 copy后
arc  堆       堆 
非arc   栈     堆   

block对局部变量与对象的区分

在之前的编译结果中,还多出了两个方法:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}

block会持有捕获的对象,包括局部变量(加了__block修饰符的)和oc对象,编译器通过BLOCK_FIELD_IS_BYREF和BLOCK_FIELD_IS_OBJECT来区分。

上面两个方法的最后一个参数都为8,表示的是BLOCK_FIELD_IS_BYREF。分别为局部变量的copy方法与释放方法。

Block学习-关于Block是如何实现的,以及block中参数传递相关推荐

  1. 深度学习论文阅读目标检测篇(七)中英对照版:YOLOv4《Optimal Speed and Accuracy of Object Detection》

    深度学习论文阅读目标检测篇(七)中英对照版:YOLOv4<Optimal Speed and Accuracy of Object Detection> Abstract 摘要 1. In ...

  2. Cadence Allegro 17.4学习记录开始25-PCB Editor 17.4软件PCB中布线操作基础

    目录 Cadence Allegro 17.4学习记录开始25-PCB Editor 17.4软件PCB中布线操作基础 一.走线和修线 走线操作步骤: 修线操作步骤: 二.Copy操作 三.chang ...

  3. 【深度学习】利用神网框架分割病理切片中的癌组织(胃)

    [深度学习]利用神网框架分割病理切片中的癌组织(胃) 文章目录 1 数据描述 2 思路 3 准备数据 4 构建模型 5 模型优化 6 程序执行 7 观察结果 1 数据描述 初赛选取癌病理切片,为常规 ...

  4. oracle修改asm参数文件,学习笔记:Oracle RAC参数文件管理 修改创建asm中的spfile文件...

    天萃荷净 Oracle rac创建修改asm中的spfile文件内容 create spfile to asm --查看sid SQL> show parameter instance_name ...

  5. 【Rust 日报】2021-11-11 保持冷静,学习Rust,我们很快就会在Linux中更多的看到这种语言...

    为Rust有很多字符串类型而烦恼吗?好吧,它没有必要 众所周知Rust有几种不同的字符串类型.两个主要的竞争者是: &str是一个 "字符串引用".它是不可调整大小的,它的 ...

  6. 微信小程序学习(三):在微信开发者工具中,使用WeUI前端美化框架,微信小程序

    微信小程序学习(三):在微信开发者工具中,使用WeUI 这里就是将WeUI导入到微信开发者工具中,我并没有使用多少样式,这里只是展示了一下怎么引用 网上有很多的方法,我绝大多数都没看懂,这里就是最简单 ...

  7. 百词斩英语单词小助手(主要实现英语单词学习的功能。用户可对词典文件中的单词进行预览,增删改查。同时还可进行中英、英中测试。本系统还提供了测试成绩的显示功能。)

    struct word //单词的结构体 {char en[MAX_CHAR]; // 英文形式char ch[MAX_CHAR]; //中文形式 } s[MAX_NUM]; //单词数组 int n ...

  8. 【杂谈】万字长文回顾深度学习的崛起背景,近10年在各行各业中的典型应用

    笔者作为一个从业5年多的技术人员,吃到了深度学习的早期红利,这次来聊一聊深度学习的崛起背景.当下的典型应用领域,算作给尚未或者正打算拥抱这门技术的朋友们一个较为全面的科普. 深度学习为什么能够崛起 一 ...

  9. Bootstrap前端框架学习(一):Bootstrap在Vue项目中的安装及可视化布局

    Bootstrap前端框架学习(一):Bootstrap在Vue项目中的安装及可视化布局 node.js的安装 bootstrap的安装 bootstrap快速布局 node.js的安装 我们需要安装 ...

最新文章

  1. vscode 高效使用指南
  2. linux 服务器 安装网卡驱动,linux下安装编译网卡驱动的方法
  3. Seata RPC 模块的重构之路
  4. 高动态范围图像HDR
  5. Web负载均衡学习笔记之四层和七层负载均衡的区别
  6. apache php url重写语法,apache url重写实现伪静态
  7. java scanner_Java Scanner nextLong()方法与示例
  8. ORACLE删除当前用户下所有的表的方法
  9. tnt_esri.dat Arcgis8.1安装license
  10. VM虚拟机,Linux系统安装tools过程遇到 what is the location of the “ifconfig” program
  11. BZOJ5212: [Zjoi2018]历史
  12. 【语音合成】基于matlab语音信号变速【含Matlab源码 565期】
  13. X86架构基本汇编指令详解
  14. 下载频道2013上半年超人气精华资源汇总
  15. VxWorks的漏洞分析与解决方案
  16. 用java实现电脑的usb功能,包括鼠标,键盘
  17. jsp页面hidden的诡异
  18. python技术分享_Python技术分享:爬虫
  19. 强制删除正在运行的文件
  20. 【python 图片文字识别】pyocr图片文字识别

热门文章

  1. 泰克示波器DPO3054自校准SPC失败维修
  2. 素材pptuku shutterstock站酷海洛创意海图片代创意矢量图片下载
  3. 用html和css制作一个简单的导航条
  4. [ORACLE]数据字典视图大全
  5. postman文档参数化
  6. Docker基础笔记(狂神说)
  7. U盘“请将磁盘插入U盘”的问题/U盘0字节修复方法
  8. 关于常成员函数对非常成员函数的调用问题
  9. Oracle-图形化界面-数据库安装
  10. GCC中 -I、-L、-l 选项的作用