iOS 底层探索篇 ——block(上)

280 阅读5分钟

这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战

1. block类型

block一共有三种类型:

  1. 全局block (NSGlobalBlock): 位于全局区,在block内部不使用外部变量,或者只使用静态变量和全局变量
  2. 堆block (NSMallocBlock): 位于堆区,在block内部使用变量或者oc属性,并且赋值给强引用或者Copy修饰的变量
  3. 栈block (NSStackBlock): 位于栈区,与MallocBlock一样,可以在内部使用局部变量或者oc属性。但是不能赋值给强引用或者copy修饰的变量。

这里不使用外部变量,所以是NSGlobalBlock。

在这里插入图片描述

这里使用局部变量,所以是NSMallocBlock。

在这里插入图片描述

这里加了__weak,a变量没有被强引用持有,所以是NSStackBlock。

在这里插入图片描述

2. block 面试题

2.1 block 捕获外部变量-对外部变量的引用计数处理

这里的引用计数会是多少呢 ?答案是5。

这里在strongBlock里面会对objc进行了捕获,就会造成objc的引用计数+1。同时strongBlock又是一个堆block,那其底部又会进行一个copy的操作,也就是从栈内存拷贝到堆内存的操作,所以又会造成objc的引用计数+1,所以总共是+2。

weakBlock的话,对objc进行了捕获,就会造成objc的引用计数+1,但是是栈block,没有进行block的copy,所以不会在进行一个+1的操作。到这里一共+3了。

void(^mallocBlock)(void) = [weakBlock copy];把block copy到了堆上,所以会造成objc的引用计数+1。这里一共加了4,所以1+4=5。

在这里插入图片描述

运行后打印发现确实是5。

在这里插入图片描述

2.2 内存拷贝的理解

这里的strongBlock1执行会发生什么呢?这里会发生崩溃,无法执行block。这里blc,weakBlock,strongBlock,strongBlock1是同一片内存空间,当blc把invoke设为nil,那么其他三个的invoke也是nil。所以这里strongBlock1无法进行调用。

在这里插入图片描述

在这里插入图片描述

这里如果对weakBlock进行copy,也就是深拷贝,那么之后weakBlock和blc的修改就不会影响到strongBlock,就不会报错了。当然,这里在invoke之前进行copy,copy之前的操作还是有影响的。

在这里插入图片描述

2.3 block 堆栈释放差异

这个地方会打印什么呢?这里的两个NSLog都会进行打印。strongBlock是栈block,这里weakBlock 和strongBlock的生命周期在blockDemo3的代码块里面,所以weakBlock可以正常执行。

在这里插入图片描述

这里如果将strongBlock变为堆block,那么运行后就会崩溃。这是因为当strongBlock变为堆block,那么weakBlock = strongBlock就会把weakBlock变成堆block,那么其生命周期就在80-87行,所以 weakBlock();调用的时候,已经出了作用域weakBlock已经变为nil了,所以就会崩溃。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3. block的循环引用

正常释放:A持有B,当A调用dealloc方法,给B发送release信号,B收到release信号,如果此时B的retainCount(即引用计数)为0时,则调用B的dealloc方法

循环引用:A、B相互持有,B持有A,导致A无法调用dealloc方法给B发送release信号,而B也无法接收到release信号,retaincount不等于0无法调用dealloc方法。所以A、B此时都无法释放。

请添加图片描述

请添加图片描述

这里面上面的block有循环引用的问题,而下面的uiviewBlock则没有。上面是self持有block,而block里面则捕获了self,所以造成self->block->self的持有链,所以会有循环引用的问题。而下面的block则是由uiview持有,uiview->block->self,没有循环引用的问题。

在这里插入图片描述

那么如何解决这个问题呢?这里加一个 __weak typeof(self) weakSelf = self;就会解决这个问题了,但是如果block中延迟执行一下,那么self.name就可能打印出一个null值。这是因为这里是异步执行,当self释放的时候,这个self.name的值就会为null。

在这里插入图片描述

那么这个问题要怎么解决呢?其实也很简单,在block里面加入一个 __strong __typeof(weakSelf)strongSelf = weakSelf;就可以。这就是weak strong dance —— 强弱共舞。这里strongSelf是临时变量,在作用域结束的时候可以自动释放。

在这里插入图片描述

那么是否还有其他方法解决循环引用的问题呢?这里也可以通过创建一个临时变量,然后手动将临时变量置空来避免循环引用。这里如果vc不置空,那么self就释放不了,就会形成循环引用。

在这里插入图片描述

还可以通过传参的方法来避免循环引用。

在这里插入图片描述

这里的staticSelf_是一个全局静态变量,这里是否会有内存泄漏问题呢?运行后发现是无法调用dealloc的,发现是有内存泄露问题的。这里为什么weakself了还有内存泄露的问题呢?这里的weakSelf和self是同一个内存空间,所以这里staticSelf_是对这同一个内存空间进行持有,staticSelf_又是一个全局静态变量,所以这里self无法进行释放

在这里插入图片描述

这里是否会有循环引用的问题呢?运行后发现,dealloc没有被执行。这里用了强弱共舞,为什么还有循环引用的问题呢?因为这里的strongSelf,到91行之行结束才会进行自动释放,但是在这之前,被weakself的doStudent捕获进去了,所以就无法释放了,造成了循环引用的问题。

在这里插入图片描述