什么是block

Block是将函数及其上下文封装起来的对象

源码分析

编译器是如何实现block的?

新建Objective-C文件命名为MyClass,在.m文件中实现如下代码:

#import "MyClass.h"
@implementation MyClass
- (void)block{int b = 10;int (^MyBlock)(int parames);MyBlock = ^int(int a){return a*b;};MyBlock(2);
}
@end

使用命令行,在MyClass.m文件目录下,运行命令

clang -rewrite-objc MyClass.m

clang 提供一个命令,可以将 Objetive-C 的源码改写成 c 语言的

在当前目录下可以看到,clang输出了一个MyClass.cpp的文件。
关键代码如下:

static void _I_MyClass_block(MyClass * self, SEL _cmd) {int b = 10;int (*MyBlock)(int parames);MyBlock = ((int (*)(int))&__MyClass__block_block_impl_0((void *)__MyClass__block_block_func_0, &__MyClass__block_block_desc_0_DATA, b));((int (*)(__block_impl *, int))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock, 2);
}struct __MyClass__block_block_impl_0 {struct __block_impl impl;struct __MyClass__block_block_desc_0* Desc;int b;__MyClass__block_block_impl_0(void *fp, struct __MyClass__block_block_desc_0 *desc, int _b, int flags=0) : b(_b) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr; //函数指针
};static int __MyClass__block_block_func_0(struct __MyClass__block_block_impl_0 *__cself, int a) {int b = __cself->b; // bound by copyreturn a*b;
}

__MyClass__block_block_impl_0就是该block的实现。

什么是block的调用

block调用即是函数的调用。

截获变量

capture过来的变量

int b = 10;int (^MyBlock)(int parames);MyBlock = ^int(int a){return a*b;};b = 100;NSLog(@"看一下结果%d",MyBlock(2));

输出结果 20
截获变量要看是对什么样的变量进行截获
有局部变量(基本数据类型、对象类型)、全局变量、静态局部变量、静态全局变量。
截获变量的特性是什么?
对于基本数据类型的局部变量截获其值。
对于对象类型的局部变量连同所有权修饰符一起截获。
以指针形式截获静态局部变量。
不截获全局变量和静态全局变量

源码分析

看下面代码


#import "MyClass.h"@implementation MyClass
// 全局变量
int global_var = 4;
// 静态全局变量
static int  static_global_var = 5;- (void)myBlockMethod{// 基本数据类型的局部变量int b = 10;// 静态局部变量static int static_var = 3;// 对象类型的局部变量 所有权修饰符__unsafe_unretained __strong__unsafe_unretained id unsafe_obj = nil;__strong id strong_obj = nil;void(^MyBlock)(void);MyBlock = ^{NSLog(@"局部变量<基本数据类型> var=%d",b);NSLog(@"局部变量<静态变量> static_var=%d",static_var);NSLog(@"局部变量<__unsafe_unretained 修饰的对象类型> var=%@",unsafe_obj);NSLog(@"局部变量 __strong 修饰的对象类型> var=%@",strong_obj);NSLog(@"全局变量 %d",global_var);NSLog(@"静态全局变量 %d",static_global_var);};MyBlock();
}@end

运行命令 clang -rewrite-objc -fobjc-arc MyClass.m
-fobjc-arc 支持ARC -fno-objc-arc 不支持ARC
关键代码如下:

int global_var = 4;
static int static_global_var = 5;struct __MyClass__myBlockMethod_block_impl_0 {struct __block_impl impl;struct __MyClass__myBlockMethod_block_desc_0* Desc;int b;int *static_var;__unsafe_unretained id unsafe_obj;__strong id strong_obj;__MyClass__myBlockMethod_block_impl_0(void *fp, struct __MyClass__myBlockMethod_block_desc_0 *desc, int _b, int *_static_var, __unsafe_unretained id _unsafe_obj, __strong id _strong_obj, int flags=0) : b(_b), static_var(_static_var), unsafe_obj(_unsafe_obj), strong_obj(_strong_obj) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void _I_MyClass_myBlockMethod(MyClass * self, SEL _cmd) {int b = 10;static int static_var = 3;__attribute__((objc_ownership(none))) id unsafe_obj = __null;__attribute__((objc_ownership(strong))) id strong_obj = __null;void(*MyBlock)(void);MyBlock = ((void (*)())&__MyClass__myBlockMethod_block_impl_0((void *)__MyClass__myBlockMethod_block_func_0, &__MyClass__myBlockMethod_block_desc_0_DATA, b, &static_var, unsafe_obj, strong_obj, 570425344));((void (*)(__block_impl *))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock);
}

我们看下下面这段代码的运行结果是什么

    int b = 2;int (^MyBlock)(int param);MyBlock = ^int(int a){return  a*b;};b = 4;NSLog(@"结果: %d",MyBlock(7));

结果如下:结果: 14
为什么是14而不是28,因为对于基本数据类型的局部变量截获其值,也就是2.

然后我们在 局部变量前面加上static

    static int b = 2;int (^MyBlock)(int param);MyBlock = ^int(int a){return  a*b;};b = 4;NSLog(@"结果: %d",MyBlock(7));

结果如下:结果: 28

为什么是28呢,因为对静态局部变量是以指针形式截获的。int b = 4;时地址上值已经被修改成4了

如果我们想在block内部对b的值进行修改,我们就要用到__block.

__block修饰符

我们什么时候要用到__block修饰符呢?
一般情况下,对被截获变量进行赋值操作需添加__block修饰符。

需要注意的是 赋值 ≠ 使用

下面看一些关于__block的一些笔试题

    NSMutableArray *arr = [[NSMutableArray alloc] init];void (^MyBlock)(NSString *str);MyBlock = ^(NSString *str){[arr addObject:str];};MyBlock(@"客户甲");NSLog(@"客户列表 %@",arr);

arr里的值是

description of arr:
<__NSArrayM 0x6000028de490>(
客户甲
)

这里不需要__block,因为这里只是使用arr,并没有赋值。
看下面这种情况

    __block NSMutableArray *arr = nil;void (^MyBlock)(NSString *str);MyBlock = ^(NSString *str){arr = [[NSMutableArray alloc] init];[arr addObject:str];};MyBlock(@"客户甲");

这里才是对arr的赋值,所以这时候需要使用__block。
对变量进行赋值具体有什么特点?
对变量进行赋值时,局部变量,不管是基本类型的局部变量还是对象类型的局部变量,都需要使用__block修饰符。
静态类型的局部变量本身就是以指针类型截获的,所以不需要。
全局变量和静态全局变量不截获,所以也不需要。
思考一下下面的代码结果是什么

    __block int b = 2;int (^MyBlock)(int param);MyBlock = ^int(int a){return a*b;};b = 4;NSLog(@"结果: %d",MyBlock(5));

结果是10还是20呢?结果: 20
好奇怪为什么不是10呢?
__block修饰符所起的作用来回答这个问题。
__block修饰的变量最后变成了对象。使用clang命令查看一下源码:

struct __Block_byref_b_0 {void *__isa;
__Block_byref_b_0 *__forwarding;//指向同类型的指针 __forwardingint __flags;int __size;int b;
};struct __MyClass__myBlockMethod_block_impl_0 {struct __block_impl impl;struct __MyClass__myBlockMethod_block_desc_0* Desc;__Block_byref_b_0 *b; // by ref__MyClass__myBlockMethod_block_impl_0(void *fp, struct __MyClass__myBlockMethod_block_desc_0 *desc, __Block_byref_b_0 *_b, int flags=0) : b(_b->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};

b变成了struct __Block_byref_b_0对象。而对b = 4;进行赋值实际上就是(b.__forwarding->b) = 4;
该段代码的运行是在栈上进行的,栈上有一个__block修饰的变量,且有一个.__forwarding的指针指向它。

Block的三种类型

在Objective-C中Block一共有三种类型:

  • _NSConcreteGlobalBlock 全局的静态block,不会访问任何外部变量;

  • _NSConcreteStackBlock 保存在栈中的block,当函数返回时被销毁;

  • _NSConcreteMallocBlock 保存在堆中的block,当引用计数器为0时被销毁

Block的内存管理

内存从高到低依次分为栈区、堆区、全局区、常量区、代码区。


需要知道的是全局的静态block保存在已初始化的全局静态区。

Block的copy操作

我们在何时需要对block进行copy操作?那么就要知道copy的效果是什么样的。

Block类别 Copy结果
_NSConcreteGlobalBlock 数据区 什么也不做
_NSConcreteStackBlock
_NSConcreteMallocBlock 增加引用计数

我们在站上有一个Block,在这个block当中,有一个__block修饰的变量,当我们对栈上的Block进行copy的时候,会在堆上生成一个和栈上一样对应的Block和__block变量,分占两块空间,左侧是在栈上,右侧是在堆上,随着变量作用域的结束,站上的Block被释放,但是堆上对应的Block和__block变量仍然存在。

请关注下面问题:
当我们对栈上的Block进行copy操作之后,假如说是在MRC环境下,是否会引起内存泄漏;答案是肯定。解析:假如说我们进行了copy操作之后,同时堆上面的这个Block没有其他成员变量指向它,那么和我们去alloc出来一个对象而没有去调用release操作是一样的。会产生内存泄漏。

究竟对栈上的__block进行copy操作,之后发生了什么呢?


我们在栈上有一个__block变量,__block下有一个__forwarding指针,栈上的__forwarding指针是指向它自身的;当对栈上的__block变量进行copy之后,实际在堆上面会产生一个__block变量,和栈上的是完全一致的,只不过分占两块内存空间。且栈上的__forwarding指针实际上指向的是堆上面的__block变量,堆上的__forwarding指针指向的是其自身。
(b.__forwarding->b) = 4;
所以,当我们对栈上__block修饰的变量b做出修改,假如说我们已经对b做了copy操作,那么我们修改的不是栈上面的__block变量b对应的值,而是通过栈上的__forwarding指针,找到它指向的堆上的__block变量b对应的值。

__forwarding指针是用来干什么的?
栈上的__forwarding是指向自身的,那么为什么需要这个指针呢?那么换句话说,我们没有这个指针的情况下,可以直接通过对成员变量的访问来对b进行赋值,那__forwading指针是不是多余了?从上面我们可以知道,当对栈上的__block变量进行copy之后,栈上的__forwading指针实际上指向堆上面的__block变量。并不是多余的。总结,不论在任何内存位置,我们都可以通过__forwading指针顺利的访问同一个__block变量。

Block的循环引用

看代码一

    _arr = [NSMutableArray arrayWithObject:@"张三"];MyBlock = ^NSString*(NSString*str){return [NSString stringWithFormat:@"你好 %@",_arr[0]];};MyBlock(@"5");

代码会报出警告⚠️
Capturing ‘self’ strongly in this block is likely to lead to a retain cycle
在这个block中,有一个对self的循环引用 。MyBlock和_arr都是当前对象self的成员变量,对_array一般用strong关键字来修饰,对block一般用copy关键字来修饰。当前对象通过copy属性关键字声明的MyBlock,所以当前对象对MyBlock有一个强引用在的。而这个Block的表达式中又使用到了当前对象的_arr成员变量,截获变量对于对象类型的局部变量是连同其所有权修饰符一起截获的。_arr是通过__strong修饰的,所以在block中就有了一个strongly类型的指针指向原来的对象

如何避免这个问题?修正代码:

   _arr = [NSMutableArray arrayWithObject:@"张三"];__weak NSArray *Weak_Array = _arr;MyBlock = ^NSString*(NSString*str){return [NSString stringWithFormat:@"你好 %@",Weak_Array[0]];};MyBlock(@"5");

采用避免产生循环引用的方式来解除它的循环引用。

那么为什么通过__weak修饰的成员变量就可以解除循环引用呢?因为block对其所截获的变量,如果是对象类型的,是连同其所有权修饰符一起截获。外部是__weak修饰符,所以里面也是__weak。

看代码二

    __block MyClass* blockSelf = self;MyBlock = ^int(int num){// b = 5return num*blockSelf.b;};MyBlock(2);

在这个栈上面,有一个__block修饰的变量来指向self,即对象本身,同时当前对象的成员变量MyBlock创建时,表达式中有使用到self.b,这里用的是blockSelf,而不是直接使用当前对象。
这段代码在MRC下,不会产生循环引用。在ARC下,会产生循环引用,引起内存泄漏。
代码中存在一个大环引用:

self对象持有Block,Block中又使用了__block变量,而这个__block 变量又指向原有的这个对象。这种循环引用就是大环引用。
怎么解除这个循环引用呢?
我们一般使用断环的方式来解除这种循环引用。断开__block变量对原对象的持有,就可以规避循环引用。

修改后的代码:

    __block MyClass* blockSelf = self;MyBlock = ^int(int num){// b = 5int result = num * blockSelf.b;blockSelf = nil;return result;};MyBlock(2);

对blockSelf进行一个nil赋值,就可以规避这个循环引用。但是这个解决方案有一个弊端,假如我们很长一段时间,或者一直都没调用这个block的话,那么这个循环引用的环就会一直存在。

小结

什么是Block?
Block是对函数及其上下文封装起来的对象。

为什么Block会产生循环引用?
第一方面,自循环引用:如果说当前Block对当前对象的某一成员变量进行截获,那么这个Block会对对应变量有一个强引用,而当前对象对Block也有一个强引用,就产生了一个自循环引用方式的循环引用问题,我们通过声明一个__weak修饰的变量,来消除循环引用。
第二方面,我们定义一个__block修饰的变量,也会产生循环引用,但是是区别场景的。在ARC下会产生循环引用,有内存泄漏;在MRC下是没有问题的。我们可以在ARC下通过断环的方式避免循环引用,但是有一个弊端,如果block一直得不到调用,那么循环引用就无法解除,环就一直存在。

怎么理解Block截获变量的特性?
对于基本数据类型的局部变量,是对值进行截获。
对于对象类型的局部变量,是连同其所有权修饰符一起截获;
对于静态类型的局部变量,是以指针形式截获的;
对全局变量和静态全局变量不截获。

你都遇到过哪些循环引用?你有事怎样解决的?
我们会遇到Block所引起的循环引用,比如
1、Block所捕获的成员变量,也是当前对象的成员变量,而block也是当前对象的成员变量,就会造成自循环的循环引用。
解决:通过加__weak修饰符开解除这种自循环引用;
2、__block在ARC下也会产生循环引用,采用断环的方式来避免循环引用。

Block相关内容梳理相关推荐

  1. (一) nvidia jetson orin nvcsi tegra-capture-vi camera相关内容梳理 之 vi相关代码分析

    背景:对于nvidia 的jetson orin 的camera,其内部是如何实现的尼?硬件方面的pipeline是怎么关联的,其内部有哪些camera相关的modules?对于这些modules,软 ...

  2. (二)nvidia jetson orin nvcsi tegra-capture-vi camera相关内容梳理 之 nvcsi,v4l2相关代码分析

    背景:对于nvidia 的jetson orin 的camera,其内部是如何实现的尼?硬件方面的pipeline是怎么关联的,其内部有哪些camera相关的modules?对于这些modules,软 ...

  3. git登录相关操作梳理

    git登录相关操作梳理 本文主要基于 Linux/Mac ,Windows下未经测试,不过估计差不多,在 git bash 内操作即可. 创建ssh key并关联github等账号 因为本地Git仓库 ...

  4. Django模板语言相关内容

    Django模板语言相关内容 Django模板系统 官方文档 常用语法 只需要记两种特殊符号: {{  }}和 {% %} 变量相关的用{{}},逻辑相关的用{%%}. 变量 在Django的模板语言 ...

  5. 多进程相关内容(IPC)

    多进程相关内容 1.守护进程(daemon) ​ 主进程创建守护进程: 1.守护进程会在主进程代码执行结束后就终止 ​ 2.守护进程内无法再开启子进程,否则抛出异常 注意:进程之间是相互独立的,主进程 ...

  6. 《清单革命》内容梳理随笔

    <清单革命>内容梳理&随笔 起 书即是将四散的知识按照逻辑和网状联系编排起来.你应该这样去读,高屋建瓴.层次有秩.显得貌似自己有经验(褒义)的读,读出一些感想和方法论,无论是读出书 ...

  7. 简历中项目部分内容梳理

    简历中项目部分内容梳理 摘要 基于检索的肺结节分类算法 基于GNN特征融合的肺结节分类方法 WEB开发 比赛 web服务器 为什么要用协程? 项目难点是哪些? 怎么实现同步的写法实现异步性能的呢? 有 ...

  8. iOS 发布APP关于IDFA的相关内容

    您的 App 正在使用广告标识符 (IDFA).您必须先提供关于 IDFA 的使用信息或将其从 App 中移除,然后再上传您的二进制文件. 如果出现下边这两张图,你就会感到蛋蛋的忧伤 还有这个 怎么解 ...

  9. golang实践LSM相关内容

    LSM LSM(log-structured merge-tree)是一种分层,有序,面向磁盘的数据结构,其核心思想是充分了利用了,磁盘批量的顺序写要远比随机写性能高出很多,在计算机科学中,日志结构的 ...

最新文章

  1. 如何在5美元的Raspberry Pi上构建个人开发服务器
  2. lightgbm的GPU版本和CPU版本运行速度比较
  3. SAP CRM text determination调试
  4. php scope权限管理,关于微信公众号scope参数错误或没有scope权限的解决方案
  5. 5月第3周回顾:08软考举行 中国遭大范围SQL注入***
  6. Office系列完全干净卸载工具合集(最全)
  7. 使用Windows Server 2003搭建ASP网站001
  8. 龙芯3a4000 交叉编译链接错误
  9. wow插件实现优雅的动画页面
  10. 企业实践|分布式系统可观测性之应用业务指标监控
  11. Stata: 正则表达式和文本分析
  12. Golang 实现定时任务
  13. 隐形眼镜的评价分析报告
  14. 这个 Spring 循环依赖的坑,90% 以上的人都不知道
  15. day 32 css后续补充以及js 简单入门
  16. 单片机tcp ip协议c语言,单片机上简单TCPIP协议的实现.PDF
  17. 马云成全球50大领袖人物排名第二,凭什么?
  18. AT24C02和AT24C32的区别 I2C调试小技巧
  19. 刘汝佳《算法竞赛入门经典(第二版)》习题(二)
  20. python把excel变成ppt_用Python写excel和ppt文件

热门文章

  1. 使用libsvm对MNIST数据集进行实验
  2. spring 源码分析之BeanPostProcessor
  3. Using the command line to manage files on HDFS--转载
  4. Spring3中js/css/jpg/gif等静态资源无法找到(No mapping found for HTTP request with URI)问题解决--转载...
  5. spring Transaction Management --官方
  6. jboss7 Java API for RESTful Web Services (JAX-RS) 官方文档
  7. funny alphabet
  8. Kaggle常用函数总结 原创 2017年07月03日 21:47:34 标签: kaggle 493 kaggle比赛也参加了好几次,在这里就把自己在做比赛中用到的函数汇总到这,方便自己以后查阅
  9. Yann LeCun:掌舵Facebook人工智能 | 完美人物志
  10. 阿里巴巴利润暴涨108%