iOS底层探索之Block(一)——初识Block(你知道几种Block呢?)

50 篇文章 6 订阅
5 篇文章 2 订阅

说在前面

Block你知道几种?Block的循环引用你有几种解决办法呢?

在上一篇博客结束了多线程锁篇章的内容,最后也带大家手写了读写锁,那么从现在开始,将开启Block的探索篇章!

1. 什么是 Block?

Block就是一个代码块, Block是将函数及其执行上下文封装起来的对象,是一个匿名的函数对象, Block也有isa。既然Block内部封装了函数,那么它同样也有参数和返回值,本身也可以被作为参数在方法和函数间传递。具体的内容,后续的博客中会重点分析,这里就先不展开了!

2. 你知道几种 block?

2.1 NSGlobalBlock

先来看看第一种Block,代码如下,猜猜打印结果是什么?

 void (^jpBlock)(void) = ^{
    };
    NSLog(@"%@",jpBlock);
  • 从下图的打印结果NSGlobalBlock可以看出来,这是一个全局的Block

NSGlobalBlock
从代码上来看,这是一个无参数也无返回值的BlockBlock体内什么也没有做,也没有引用任何的变量,总结来说如下:

GlobalBlock

  • 位于全局区
  • Block内部不使用外部变量,或者只使用静态变量全局变量

2.2 NSMallocBlock

看看如下这个代码,打印是什么Block

int a = 10;

void (^jpBlock)(void) = ^{
   NSLog(@"输出:%d",a);
};
NSLog(@"%@",jpBlock);
  • 打印输出结果如下:

NSMallocBlock
这里引用了一个外部的变量,打印是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);
  • 打印结果

NSStackBlock

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

  • strongBlockNSMallocBlock,它的生命周期是在代码块{}的里面,出了代码块的作用域就会被释放。
  • 在代码块中strongBlock赋值给weakBlock是属于指针拷贝,此时指向了对应的NSMallocBlock,但是并没有强引用指向这个block
  • strongBlock所在的代码块执行完毕后,该NSMallocBlock就会被释放掉,此时调用weakBlock()指向的对象已经被释放了,形成野指针,所以程序崩溃了。

那么该如何解决这个问题呢?请看下面的代码

解决崩溃
上面的例子崩溃了,是因为堆区的 block出了作用域,被释放了,那么现在把两个block__weak都去掉,则能够正常运行并打印结果

因为此时的赋值,weakBlock对堆中的block进行了强引用,代码块运行结束后不会释放掉,也就不存在野指针的问题了。

4.总结

  • block分为全局 block堆 block栈 block
  • block可以捕获外部变量
  • 堆区的 block捕获外部变量会拷贝到堆区引计数+1
  • block 在使用的时候,堆block注意作用域的问题,弱引用指针赋值,出了作用域就释放了,可以通过强引用解决

下篇预告:

那强引用释放不掉,出现循环引用,该怎么解决呢?下一篇将针对 block的循环引用,做出分析和解决办法!

更多内容持续更新

🌹 喜欢就点个赞吧👍🌹

🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡卡西Sensei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值