block- 三种block__NSGlobalBlock__ NSStackBlock __NSMallocBlock __
自己测试的数据:
测试时在buildSetting
中将ARC关掉,因为在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上。
/****** __NSGlobalBlock__ (没有访问auto变量)******/
// 什么参数也不引用
NSLog(@"%@",^{NSLog(@"*******");} );
// 引用全局变量或者全局静态变量
NSLog(@"%@", ^{NSLog(@"%d, %d", a, global_i);});
// 引用局部静态变量
NSLog(@"%@", ^{NSLog(@"%d", static_local_k);});
// 被强指针引用,并且只引用了全局变量或静态变量
strongBlock = ^{NSLog(@"%d", a);};
NSLog(@"%@", strongBlock);
/******__NSStackBlock (访问了auto变量)******/
// 只引用局部变量
NSLog(@"%@", ^{NSLog(@"%d%d%d", a, static_local_k, static_global_j);});
// 引用局部变量和全局变量(或静态变量)
NSLog(@"%@", ^{NSLog(@"%d, %d", a, global_i);});
/****** __NSMallocBlock (__NSStackBlock 执行copy操作)******/
// 被强指针引用,并且引用了局部非静态变量
strongBlock = ^{NSLog(@"%d%d", a, global_i);};
NSLog(@"%@", strongBlock);
// 被强指针引用,并且引用了局部非静态变量,和全局变量(或静态)
strongBlock = ^{NSLog(@"%d%d", a, global_i);};
NSLog(@"%@", strongBlock);
总结:
NSGlobalBlock(不持有对象)
以下总结基于MRC条件下,因为在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上:
- 只要不引用auto变量,那么这个block就是
__NSGlobalBlock
。
NSStackBlock(不持有对象)
- 只要引入auto变量,那么这个block就是
__NSStackBlock
。
NSMallocBlock(持有对象)
- __NSStackBlock 执行copy操作。
Block的副本:
Block的类 | 副本源的配置存储域 | 复制效果 |
---|---|---|
__NSConcreteStackBlock | 栈 | 从栈复制到堆 |
__NSConcreteGlobalBlock | 程序的数据区(全局区) | 什么也不做 |
__NSConcreteMallocBlock | 堆 | 引用计数增加 |
不管block配置在何处,用copy方法复制都不会引起任何问题。在不确定时调用copy方法即可。(浪费CPU资源)
block的copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上(即执行copy操作),比如以下情况:
- block作为函数返回值时
- 将block赋值给
__strong
指针时 - block作为Cocoa API中方法名含有
usingBlock
的方法参数时 - GCD中用到的block参数
当block内部访问了对象类型的auto变量
无论是ARC还是MRC,
- NSStackBlock(即block在栈上)中访问对象类型的自动变量时,block不会持有该对象。
- NSMallocBlock(即block被copy到堆上)中访问对象类型的自动变量时,block会持有该对象,即该对象的引用计数会加1.
分析:
如果block被拷贝到堆上,
首先会调用block内部的copy函数,
然后copy函数内部会调用_Block_object_assign
函数,
_Block_object_assign
函数会根据auto变量的修饰符(__strong,__weak,__unsafe_unretained)做出相应的操作,类似retain(形成强引用,弱引用)
如果block从堆中移除:
会调用block内部的dispose函数,
dispose函数内部会调用_Block_object_dispose
函数,
_Block_object_dispose函数会自动释放引用的auto变量,类似于release
在block内修改外部变量的方式
- 静态变量可以在block内直接修改
- 全局变量可以在block内直接修改
- 使用__block修饰符修饰auto变量,可以在block内修改
__block修饰符
- __block可以用于解决block内部无法修改auto变量值的问题
- __block不能修饰全局变量,静态变量(static)
- __block 会将变量包装成一个对象
__block的内存管理
- 当block在栈上时,并不会对
__block
变量产生强引用 - 当block被copy到堆上时,会调用block内部的copy函数,copy函数内部会调用
_Block_object_assign
函数会对__block变量形成强引用
- 当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部会调用
_Block_object_dispose
函数,_Block_object_dispose
函数会自动释放引用的__block变量(release)
当Block
被复制到堆上时,__block
变量也被复制到堆上,被Block
持有。
当__block
变量同时被两个Block
持有时,会增加__block
的引用计数。
总结-对象类型的auto变量和__block变量
被__block修饰的对象类型(对象类型😯)
- 当__block变量在栈上时,不会对指向的对象产生强引用
- 当__block变量被copy到堆上时,会调用__block变量内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据所指向对象的修饰符(__strong,__weak,__unsafe_unretained)做出相应的操作,类似retain(形成强引用,弱引用)(!!!:这里仅限于ARC时会retain,MRC时不会retain)
在MRC下:(如下图)block还没释放,person对象就dealloc了
__block变量结构体中__forwarding指针的作用
先给个结论:
__forwarding
指向__block
变量本身。当__block
变量在栈上的时候,__forwarding
指向__block
在栈上的地址;当__block
被复制到堆上后,栈上__block
变量的__forwarding
指向__block
在堆上的地址,同样,堆上__block
变量的__forwarding
指针也指向它在堆上的地址,这样,当在函数中改变__block
变量时,访问的便是同一个__block
变量。细节看下面代码。
typedef void (^blk_t)(void);
__block int val = 0;
blk_t blk = [^{++val;} copy]; // 可转化为++(val.__forwarding->val)
++val; // 可转化为++(val.__forwarding->val)
blk();
NSLog(@"val:%d", val);
上述代码执行后val的值变为2
block中引用self时会不会造成循环引用
- 首先考虑block的类型,
__NSStackBlock__
不会持有对象,所以不会造成循环引用;如果是__NSMallocBlock__
,则会持有对象,所以会导致循环引用。
解决循环引用-ARC
三种方法: