本文重点总结 OC block 的原理,并带上一些例子,不讨论 block 的写法和应用。
block 的本质总结如下:
- block 在底层上是一个结构体,内部有一个
isa
指针,指向block
所属的类,其父类最终指向NSObject
,所以可以把block
看做对象:
void (^ blk)(void) = ^{
NSLog(@"hello");
};
NSLog(@"%@", [blk class]); // __NSGlobalBlock__
NSLog(@"%@", [[blk class] superclass]); // __NSGlobalBlock
NSLog(@"%@", [[[blk class] superclass] superclass]); // NSBlock
NSLog(@"%@", [[[[blk class] superclass] superclass] superclass]); // NSObject
- block 有三种类型:_NSConcreteGlobalBlock(全局block), _NSConcreteStackBlock (栈block), _NSConcreteMallocBlock (堆block),分别储存在data区,栈区,堆区,他们的类型由创建方式以及创建时候捕获的变量类型决定。
- 不使用外部变量的block是全局block,使用外部变量并且未进行copy操作的block是栈block,对栈block进行copy操作,就是堆block,而对全局block进行copy,仍是全局block。
// 不使用外部变量
NSLog(@"%@", [^{
NSLog(@"globalBlock");
} class]); // __NSGlobalBlock__
// 使用外部变量并且未进行copy
NSInteger num = 10;
NSLog(@"%@", [^{
NSLog(@"stackBlock:%zd", num);
} class]); // __NSStackBlock__
// 对全局block进行copy
void (^ globalBlock)(void) = [^{
NSLog(@"globalBlock");
} copy];
NSLog(@"%@", [globalBlock class]); // __NSGlobalBlock__
// 对栈block进行copy操作
void (^ mallocBlock)(void) = [^{
NSLog(@"stackBlock:%zd", num);
} copy];
NSLog(@"%@", [mallocBlock class]); // __NSMallocBlock__
-
为了解决block所在变量域结束后block仍然可用的问题,需要把栈block复制到堆上。
-
ARC时,在四种情况下栈block会自动复制到堆上
- 手动copy
- block作为函数返回值
- block赋值给__strong修饰的变量或Block类型成员变量时
- 向Cocoa框架含有usingBlock的方法或者GCD的API传递Block参数时 )
-
MRC只有手动copy,才会复制到堆上
-
block可以捕获他周围的变量,分为:全局变量、自动变量(函数栈上的默认变量)、静态变量(函数栈上加
static
关键字的变量)、__block
变量(函数栈上加__block
关键字的变量) -
全局变量,因为作用域范围广,所以可以在block内改变它们的值。
-
block捕获的自动变量又分为基本数据类型变量(如
int
,double
)和指针型变量(如:对象),捕获的是自动变量的值,不能在block内部改变自动变量的值。 -
block捕获的静态变量是变量的地址。通过使用静态变量的指针对其进行访问,可以在block内改变值。
-
block变量在底层是一个结构体,也有isa指针,可以看成一个对象。
-
block捕获
__block
变量,捕获的是对应结构体的变量的地址,可以在block内直接改变值。 -
当block复制到堆上,block使用到的__block变量也会被复制到堆上并被block持有。
-
__block
变量结构体内有__forwarding
指针,栈上的__forwarding
指针会指向堆上的__forwarding
变量,而堆上的__forwarding
指针指向其自身,所以如果对__block
的修改(栈上的还是堆上的),实际上永远只会修改堆上的__block
变量。 -
当block复制到堆上后,其对获取的变量的关系如下:->表示持有
- 自动变量:堆Block -> 变量的值 (不能改变)
- 静态变量:堆Block -> 变量的地址(可以改变变量)
-
__block
普通基本数据类型变量:堆Block -> 堆__block
变量 (此时变量的值被复制到了堆了,可以改变变量的值) -
__block __strong
对象类型变量: 堆Block -> 堆__block
变量 -> 对象 (对象本身就是在堆上,可以改变对象的值)
-
堆上的block如果捕获了强引用对象,那个对象又引用了block,会照成循环引用
-
可以用
__weak
修饰应用block的对象,避免循环引用 -
假设
self
引用了block,用__weak
修饰(__weak typeof(self) weakSelf = sellf;
)避免循环引用,如果block执行完成之前,self
被释放了,weakSelf
也会变为 nil,可能引起奔溃,我们在block中使用__strong
修饰weakSelf
保证任何情况下,self
在超出作用域后仍能够使用,防止self
的提前释放:__strong typeof(weakSelf) strongSelf = weakSelf
网友评论