说在前面
Block
你知道几种?Block
的循环引用你有几种解决办法呢?
在上一篇博客结束了多线程
的锁篇章
的内容,最后也带大家手写了读写锁
,那么从现在开始,将开启Block
的探索篇章!
1. 什么是 Block?
Block
就是一个代码块, Block
是将函数及其执行上下文封装起来的对象,是一个匿名的函数对象, Block
也有isa
。既然Block
内部封装了函数,那么它同样也有参数和返回值,本身也可以被作为参数在方法和函数间传递。具体的内容,后续的博客中会重点分析,这里就先不展开了!
2. 你知道几种 block?
2.1 NSGlobalBlock
先来看看第一种Block
,代码如下,猜猜打印结果是什么?
void (^jpBlock)(void) = ^{
};
NSLog(@"%@",jpBlock);
- 从下图的打印结果
NSGlobalBlock
可以看出来,这是一个全局的Block
。
从代码上来看,这是一个无参数也无返回值的Block
,Block
体内什么也没有做,也没有引用任何的变量,总结来说如下:
GlobalBlock
:
- 位于全局区
- 在
Block
内部不使用外部变量
,或者只使用静态变量
和全局变量
2.2 NSMallocBlock
看看如下这个代码,打印是什么Block
?
int a = 10;
void (^jpBlock)(void) = ^{
NSLog(@"输出:%d",a);
};
NSLog(@"%@",jpBlock);
- 打印输出结果如下:
这里引用了一个外部的变量,打印是NSMallocBlock
,也就是堆Block
。
NSMallocBlock
:在
Block
内部使用局部变量或者OC
属性,并且赋值给强引用或者Copy
修饰的变量。
2.3 NSStackBlock
NSStackBlock
:
- 位于栈区
- 与
MallocBlock
一样,可以在内部使用局部变量或者OC
属性。 - 但是不能赋值给强引用或者
Copy
修饰的变量
int a = 10;
void (^__weak jpBlock)(void) = ^{
NSLog(@"输出:%d",a);
};
NSLog(@"%@",jpBlock);
- 打印结果
在
ARC
环境下,使用_ _weak
修饰就是一个栈Block
,如上图代码所示,打印结果也可以验证是栈Block
。
既然三种Block
都已经介绍完了,那么接下来举几个面试题看看。
3. Block
面试题举例
3.1 例子 1
block
捕获外部变量——对外部变量的引用计数处理
代码如下:
NSObject *objc = [NSObject new];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1
void(^strongBlock)(void) = ^{
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
void(^__weak weakBlock)(void) = ^{
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
weakBlock();
void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();
- 打印结果如下:
第一个打印1
都是很好理解,就是后面的打印有点懵,那么解答如下:
strongBlock
是一个堆区的block
,这里捕获了objc
这个外部变量会进行加一,这里会把栈区的objc
拷贝到堆区又进行了加一,所以打印结果为3
。- 打印
4
是因为这里weakBlock
是栈block
,没有进行拷贝(底层源码可以验证,这里就不展开了,后续会对底层源码进行分析)只是捕获+1
,所以为4
。 - 最后打印
5
是因为[weakBlock copy]
进行了拷贝操作,再赋值给mallocBlock
也是+1
操作,所以打印结果为5
。
3.2 例子 2
block
堆栈释放差异举例,猜猜是否可以正常打印?
代码如下:
#pragma mark - block 堆栈释放差异
- (void)jpreno {
int reno = 10;
void(^__weak weakBlock)(void) = nil;
{
// 栈区
void(^__weak strongBlock)(void) = ^{
NSLog(@"jp1:---%d", reno);
};
weakBlock = strongBlock;
NSLog(@"jp2:--%@--%@",weakBlock,strongBlock);
}
weakBlock();
}
- 代码运行结果如下:
这里估计很多人会有疑惑,这种赋值还是第一次见,但是面试的使用肯定会遇到各种奇奇怪怪的题目的,这里讲解了,以后就不怕了😁。
- 这里声明了一个
weakBlock
,是属于NSStackBlockl
类型的block
在代码块{}
中,定义了strongBlock
,也是NSStackBlock
类型的block
。 - 然后对
weakBlock
进行了赋值操作,此时两个block
均指向同一个NSStackBlock
。 - 这两个栈
block
的生命周期到jpreno
方法的运行结束(也就是离开方法{}
作用域)才释放,这里并不会被提前释放,所以调用weakBlock()
可以正常运行,打印出结果。
那么把代码改一下,看看结果如何呢???
代码改后运行崩溃了,什么鬼啊?哪里改了啊?我怎么没有发现哪里改动了呢!这里把
strongBlock
的__weak
给去掉了就变成了NSMallocBlock
。
strongBlock
为NSMallocBlock
,它的生命周期是在代码块{}
的里面,出了代码块的作用域就会被释放。- 在代码块中
strongBlock
赋值给weakBlock
是属于指针拷贝,此时指向了对应的NSMallocBlock
,但是并没有强引用指向这个block
。 strongBlock
所在的代码块执行完毕后,该NSMallocBlock
就会被释放掉,此时调用weakBlock()
指向的对象已经被释放了,形成野指针,所以程序崩溃了。
那么该如何解决这个问题呢?请看下面的代码
上面的例子崩溃了,是因为堆区的 block
出了作用域,被释放了,那么现在把两个block
的__weak
都去掉,则能够正常运行并打印结果
因为此时的赋值,
weakBlock
对堆中的block
进行了强引用,代码块运行结束后不会释放掉,也就不存在野指针的问题了。
4.总结
block
分为全局 block
、堆 block
、栈 block
block
可以捕获外部变量- 堆区的
block
捕获外部变量会拷贝到堆区引计数+1 block
在使用的时候,堆block
注意作用域的问题,弱引用指针赋值,出了作用域就释放了,可以通过强引用解决
下篇预告:
那强引用释放不掉,出现
循环引用
,该怎么解决呢?下一篇将针对block
的循环引用,做出分析和解决办法!
更多内容持续更新
🌹 喜欢就点个赞吧👍🌹
🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹