iOS底层探索之Block(一)——初识Block(你知道几种Block呢?)
说在前面
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、栈 blockblock可以捕获外部变量- 堆区的
block捕获外部变量会拷贝到堆区引计数+1 block在使用的时候,堆block注意作用域的问题,弱引用指针赋值,出了作用域就释放了,可以通过强引用解决
下篇预告:
那强引用释放不掉,出现
循环引用,该怎么解决呢?下一篇将针对block的循环引用,做出分析和解决办法!
更多内容持续更新
🌹 喜欢就点个赞吧👍🌹
🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹
转载自:https://juejin.cn/post/7000970261524643870