Block小知识点
Block小知识点
前言
Block是开发过程中常用的便捷回调方式,下面我们就做下总结。
Block介绍
什么是Block
- Block是将
函数
及其执行上下文
封装起来的对象
。
源码解析
- 使用
【clang -rewrite-objc file.m】
查看编译之后的文件内容
什么是Block调用
- Block调用即是函数的调用。
以下面代码为例:
- (void)show
{
int num = 10;
int(^Block)(int) = ^int(int factor) {
return num * factor;
};
Block(2);
}
查看源码:
// Block对应的定义
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __DCBlock__show_block_impl_0 {
struct __block_impl impl;
struct __DCBlock__show_block_desc_0* Desc;
int num;
__DCBlock__show_block_impl_0(void *fp, struct __DCBlock__show_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 封装了block执行逻辑的函数
static int __DCBlock__show_block_func_0(struct __DCBlock__show_block_impl_0 *__cself, int factor) {
int num = __cself->num; // bound by copy
return num * factor;
}
// block的相关描述
static struct __DCBlock__show_block_desc_0 {
size_t reserved;
size_t Block_size;
} __DCBlock__show_block_desc_0_DATA = { 0, sizeof(struct __DCBlock__show_block_impl_0)};
static void _I_DCBlock_show(DCBlock * self, SEL _cmd) {
int num = 10;
/*
int(^Block)(int) = ^int(int factor) {
return num * factor;
};
*/
int(*Block)(int) = ((int (*)(int))&__DCBlock__show_block_impl_0((void *)__DCBlock__show_block_func_0, &__DCBlock__show_block_desc_0_DATA, num));
/*
Block(2);
*/
((int (*)(__block_impl *, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, 2);
}
- isa:isa指针,Block是对象的标志
- FuncPtr:函数指针,指向调用函数的地址
截获变量
根据数据类型,分为以下几种:
- 对于
基本数据
类型的局部变量
截获其值 - 对于
对象
类型的局部变量连同所有权修饰符
一起截获 - 以
指针形式
截获局部静态
变量 不截获
全局变量、静态全局变量
- 作用域:
局部
变量类型 | 是否捕获到block内部 | 访问方式 |
---|---|---|
基本数据类型 | 是 | 值传递 |
对象类型 | 是 | 值传递 |
静态变量 | 是 | 指针传递 |
- 作用域:
全局
变量类型 | 是否捕获到block内部 | 访问方式 |
---|---|---|
变量、静态变量 | 否 | 直接访问 |
-
为什么局部变量需要捕获? 考虑作用域的问题,需要跨函数访问,就需要捕获
-
block的变量捕获 为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
-
block里访问self是否会捕获? 会,self是当调用block函数的参数,参数是局部变量,self指向调用者
-
block里访问成员变量是否会捕获 会,成员变量的访问其实是
self->xx
,先捕获self,再通过self访问里面的成员变量
__block修饰符
一般情况下
,对被截获变量进行赋值
操作需添加__block修饰符
- 作用:__block修饰的变量会变成对象
__block int num = 4;
num = 6; // num.__forwarding->num=6
Block的内存管理
对于Block本身有三种类型:
- _NSConcrete
Global
Block:全局 - _NSConcrete
Stack
Block:栈 - _NSConcrete
Malloc
Block:堆
Block的Copy操作
Block类别 | 源 | Copy结果 |
---|---|---|
_NSConcreteGlobalBlock | 数据区 | 什么也不做 |
_NSConcreteStackBlock | 栈 | 堆 |
_NSConcreteMallocBlock | 堆 | 增加引用计数 |
- 变量作用域结束后
- 栈上的创建的Block和__block变量会被销毁
- 当栈上的Block销毁后,堆上的Block和__block变量依然存在
__forwarding
指针是用来干什么的- 栈上__block变量的Copy
只是在栈的Block,__block变量的__forwarding指向的是栈上__block变量本身
对栈的Block进行copy操作后,栈上__block变量的__forwarding指向的是堆上__block变量;堆上__block变量的__forwarding指向的是堆上__block变量本身
- __forwarding存在的意义 不论在任何内存位置,都可以顺利的访问同一个__block变量。
Block的循环引用
循环引用的实质:多个对象相互之间有强引用,不能释放让系统回收。
- 在ARC下:
方法 | 比较 |
---|---|
__weak | 不会产生强引用,指向的对象销毁时,会自动让指针置为nil |
__unsafe_unretained | 不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变 |
__block | 必须把引用对象置位nil,并且要调用该block |
- 在MRC下:
方法 | 比较 |
---|---|
__unsafe_unretained | 不会产生强引用 |
__block | 不会产生强引用 |
总结
- 什么是Block?(定义)
- 为什么Block会产生循环引用?
- 自循环引用:如当前Block对当前对象的某一成员变量进行截获,则会对成员变量有一个强引用;而当前对象对Block也有一个强引用,这样就会产生一个自循环引用方式的循环引用问题;我们可以声明对象为__weak变量,来避免循环引用
- 大环循环引用:如果定义了一个__block说明符,在ARC会产生循环引用,可以通过
断环
的方式来解决循环引用,不过有一个弊端,如果block一直无法得到调用的话,这个循环问题是无法得到解决的
- 怎样理解Block截获变量的特性?(从变量作用域和类型考虑)
- 你都遇到过哪些循环引用?你又是怎样解决的?
- Block
- NSTimer
参考文章
最后
如果对大家有帮助,请github上follow和star,本文发布在戴超的技术博客,转载请注明出处