int multiplier = 6
int(^ Block)(int) = ^int(int num){
return num* multiplier
};
Block(2);
Block
"Block是什么"
是将函数及其执行上下文封装起来的对象
内部有isa指针和FuncPtr函数指针
isa说明他是个对象,FuncPtr指针指向了函数实现
"Block调用是什么"
就是函数调用
当我们调用block(2)时,内部实现是
通过block结构体里面的函数指针,取出对应的执行体.将参数传递进来(block本身,2),然后进行内部调用
源码分析
"Block源码结构体"中包括以下内容,说明了Block为什么是对象以及是怎么将函数和执行上下文封装的
-block_impl结构体
isa指针
FuncPtr函数指针,指向我们在block花括号中的执行体
-block相关描述的结构体
-block中传进来的参数(局部变量)
"截获变量的特性的内部实现"
最上面的示例中,当截获了变量multiplier,是把它传到上述的block结构体中
注意❤️:一旦在block结构体中赋值了,再操作时就是对结构体中的变量操作了,不是对block外的变量操作了
若传进来:
局部变量基本数据类型 - 在block结构体中,直接截获这个值,赋值给block内部使用
局部变量对象类型(不知道为啥成员变量也截获) - 在block结构体中,连同对象的修饰符一起截获,赋值给block内部使用
注意❤️:block的循环引用,就是因为局部对象是联通修饰符一起截获的
静态局部变量 - 在block结构体中,以指针形式截获,也就是说,如果在block的定义之后,对静态局部变量值进行修改,再调用block时,用的是最新的值
全局变量 - 不在block结构体中
静态全局变量 - 不在block结构体中
"截获变量总结"
局部变量基本数据类型 - 直接截获,传进来是什么就是什么,不会改变
局部变量对象类型 - 连同所有权修饰符一起截获
静态局部变量 -指针截获,值会改变
全局变量 -不截获,值会改变
静态全局变量 -不截获,值会改变
__block修饰符
对截获变量进行赋值操作时需要添加__block
注意❤️:赋值不等于使用!!!
array = [NSMutableArray array]就是赋值,若在block内部调用的话,需要为外部的array声明添加__block修饰符
[array addObject:@!23]就是使用而不是赋值
"如何使用__block修饰符"
"以下变量的赋值操作,需要使用__block修饰的情况"
添加__block之后,当外部变量值改了之后,block内部调用时也会更改
1. 在block内部对局部变量基本数据类型进行赋值操作时
2. 在block内部对局部变量对象数据类型进行赋值操作时
"以下变量的赋值操作,不需要__block修饰"
1. 在block内部对静态局部变量进行赋值操作时
2. 在block内部对全局变量进行赋值操作时
3. 在block内部对静态全局变量进行赋值操作时
因为全局变量和静态全局变量都不涉及截获操作
静态局部变量是通过指针来使用的,操作的是block外部的变量,所以不需要修饰
"__block做了什么"
__block修饰的变量会变成对象
举个🌰,当我们执行这句代码"__block int num"之后 ,num不再是个int型,而是变成个结构体,包含如下
1. void* isa
2. int num
3. __forwarding指针
"_forwarding指针"
存在的意义
不论在任何内存位置,我们都可以通过_forwarding指针顺利的访问同一个__block变量
若没有对__block进行copy,那么操作的是栈上的__block变量
如果copy后,不论是在栈还是堆,我们对__block的修改活赋值,都是对堆上的__block进行的
栈上的__block变量的__forwarding指针,是指向__block自身
当block外部的num改变时,__forwarding指针会去block结构体中找到里面的num对象进行赋值,但要注意这是栈上的block才会这样
如下图
如果对__block变量进行copy操作后,会在堆上面产生完全一样的block变量
栈上的__forwarding指针指向堆上的__block变量,而堆上的__forwarding指针指向自身的__block
所以说,在经过了copy之后,只要对这个值进行了修改,__forwarding指针改的都是堆上的值
如下图
Block的内存管理
"block有哪几类"
impl.isa = NSConcteteStackBlock, isa会标记block是哪种类型
全局block = NSConcreteGlobalBlock 存放在内存的已初始化数据区域中
栈block = NSConcreteStackBlock 存放在内存的栈上面
堆上面的block = NSConcreteMallocBlock 存放在内存的堆上面
"对于block的copy操作"
全局block - copy后什么也不做
栈block - copy后会在堆上产生一个block
堆block - copy后会增加其引用计数
问题🛫:
P类中有个assign修饰的block,假如在方法A中,我们P.block = ^(int){***}
因为方法A是在栈上,执行完在内存中就销毁了,假如在后面我们又调用了P.block
就会崩溃!!!
"block的销毁"
栈block变量在作用域结束后就销毁了
当我们对栈上block进行copy之后,会在堆上产生一模一样的blcok,但分占了栈和堆两块内存空间,当作用域结束后,栈上的block会销毁,但堆上的不销毁
在MRC环境下,对栈上block进行copy后,会内存泄露,因为如果堆上的block没有其他变量指向,就会产生内存泄露
Block的循环引用
"为什么会产生循环引用"
在截获变量中,若对象P强引用block,block内部又截获了strong类型的数组对象,就会循环引用
"怎么打破循环引用"
__weak
__block修饰符引起的循环引用
注意MCBlock不是个block,只是个类名字
上图的问题是,在MRC下不会产生循环引用,但在ARC下会产生循环引用,引起内存泄露
解决方式
下图这个解决方案有个弊端,也就是如果我们很长时间都没有调用这个block的话,这个循环引用就一直存在