这是我参与8月更文挑战的第20天,活动详情查看: 8月更文挑战
block的分类
iOS中block分为三类:
NSGlobalBlock:全局block- 位于全局区;
- 在
block内部不使用外部变量,或者只是用静态变量和全局变量;
NSMallocBlock:堆block- 位于堆区;
- 在
block内部使用局部变量或者OC属性,并且赋值给强引用;
NSStackBlock:栈block- 位于栈区;
- 与
NSMallocBlock一样,可以在内部使用局部变量或者OC属性。但是不能赋值给强引用或者Copy修饰的变量;
block的运行
NSGlobalBlock:全局block,内部不使用外部变量NSMallocBlock:堆block,内部使用变量aNSStackBlock:栈block,使用__weak进行弱引用(默认为强引用)
需要注意的是:
block持有的是^{}的内存空间;
block操作引用计数
我们在使用block的过程中,经常会出现循环引用的问题,继而导致出现内存泄漏,视图无法释放dealloc不执行的问题;所以我们要熟练掌握不同的block对变量的持有情况;
解析:
- 1、
[NSObject new]之后,其引用计数为1;相信这一步打印大家应该是没有异议的; - 2、
strongBlock打印3应该是出乎大家意料的,这是为什么呢?- 首先:
strongBlock会捕获objc,从而导致objc的引用计数+1; - 但是因为
strongBlock内部使用了objc,并且是默认的强引用,所以strongBlock是个堆区的block;由于strongBlock是一个非全局的block,那么进行操作时会进行block内存的开辟一个result,然后会将原始的block也就是ablock的size,invoke,flags和isa等成员变量都会赋值给result一份,相当于将aBlock也就是原始的block拷贝了一份给result这个新开辟了内存空间的block;那么此时a因为被strongBlock强持有,会把栈的内存拷贝了一份到堆上去,那么其引用计数也要再+1,所以最终是3;这部分分析,可在block的底层源码中看到:
- 首先:
- 3、
weakBlock因为使用了__weak进行修饰,那么他就是个栈block,栈block不进行内存拷贝的操作;其引用技术只能+1为4; - 4、
weakBlock拷贝了一份放在名为mallocBlock的堆block上,所以引用计数再次+1为5;
此处,其实第二步操作可以看作是第三步和第四步操作合体;第二步分解之后就是第三步和第四步操作;
block的内存拷贝
为了便于操作block的成员变量,我们按照系统block来重写了一个block的结构;那么我们就可以对系统的block进行强转,然后操作成员变量;
我们来看一段代码:
_LGBlock是我们重写的block的结构struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;将系统的block强制转换为_LGBlock;blc->invoke = nil;这里我们想要论证一个点:我们强转之后的blc和weakBlock是不是同一片栈的内存空间?如果是,那么对blc的invoke进行=nil操作,那么weakBlock也会被修改,将无法调用;void(^strongBlock1)(void) = strongBlock;因为我们把strongBlock声明为了id,此处这么写是为了使strongBlock具有block的特性,可以使用strongBlock()进行调用;其实可以直接写成void(^strongBlock1)(void) __strong strongBlock = weakBlock;
我们来看一下代码的执行结果:
代码崩溃,表明
invoke置空之后导致block找不到了;
我们对代码进行调整之后,再次运行:
进行copy操作之后,strongBlock的内存将会从栈上拷贝到堆上,那么strongBlock与weakBlock已经不是同一片内存了;
分析到这里,我们应该对
栈block和堆block有一定的认知了;
block堆栈的释放
我们接着看一段代码的执行:
- 因为
strongBlock使用了局部变量,但是使用了__weak进行弱引用,所以strongBlock是个栈block,然后将strongBlock赋值给了weakBlock,所以此时weakBlock指向了栈block的内存空间,而strongBlock在所在代码块执行完毕之后就被释放;但是weakBlock的生命周期是在blockDemo3的整个代码区;所以虽然strongBlock最已经释放,但是由于weakBlock指向了其内存空间,最后依然能够正常打印出1 0;
代码执行如下:
我们将代码稍作修改,去掉strongBlock的__weak,查看运行结果:
直接崩溃!!!Why???
解析:
- 去掉了
__weak的strongBlock成为了堆block,其生命周期为所在的代码块区域; strongBlock赋值给了weakBlock,而weakBlock是一个堆block,所以会将strongBlock的指针拷贝给了weakBlock,他们两个的指针地址是一样的;当出了strongBlock的作用域之后,strongBlock就会释放,那么weakBlock所指向的内存空间将会不存在,所以会崩溃;
如图所示:
block的循环引用
-
可以正常释放
-
循环引用无法释放
接下来我们来看段代码:
UIView持有了block,block捕获的self,所以不会造成循环引用;
self持有了block,block又捕获了self,然后导致了retain cycle,造成循环引用;
那么我们怎么解决这个循环引用呢?
我们可以通过__weak typeof(self) weakSelf = self;来解决循环引用;
那么是不是通过__weak typeof(self) weakSelf = self;就能一劳永逸呢?我们再来看一段代码:
block中有一个延迟2秒执行的任务,我们在当前界面停留超过2秒在进行pop返回上级界面时,一切正常;
那么如果我们在2秒内就返回呢?
可以看到,如果我们快速的返回上级界面,dealloc执行之后,name值就获取不到了;因为weakSelf已经释放了,nil发送消息还是nil;
那么如何解决呢?我们继续修改代码如下:
此时,即使我们快速的返回上级界面,dealloc执行,name也能够正常打印;__weak typeof(self) weakSelf = self;与__strong typeof(weakSelf) strongSelf = weakSelf;结合使用可以避免数据的丢失;
strongSelf是一个临时变量,在作用域执行完毕之后就会释放;weakSelf可以自动置为nil;
那么除此之外,还有没有其他方式可以避免循环引用呢?
我们修改代码如下:
此时,即使我们快速的返回上级界面,dealloc执行,name也能够正常打印;
解析:
vc是一个临时变量,对self进行持有;其生命周期只在NSLog之前有意义,然后进行=nil;- 如果不进行
=nil操作;那么self持有block,block持有vc,vc又持有self,最终导致循环引用;
除此之外,我们还可以通过参数的方式来解决循环引用;
我们修改block的定义,把typedef void(^KCBlock)(void);修改为typedef void(^KCBlock)(ViewController *);,代码修改如下:
正常运行,可以释放;
- 因为
vc是以参数的形式传递的,所以并不会被block捕获;
block循环引用的面试题
面试题一
static ViewController *staticSelf_;
- (void)blockWeak_static {
__weak typeof(self) weakSelf = self;
staticSelf_ = weakSelf;
}
上面方法是否会发生循环引用?答案是会发生循环引用!!!
weakSelf是个弱引用,那么为什么还会发生内存泄漏导致无法释放呢?
解析:
weakSelf和self是同一片内存空间,是映射关系;可以通过打印进行验证:
那么staticSelf持有的实际上是self,而staticSelf是一个全局的静态变量,所以self无法释放;
面试题二
@property (nonatomic, copy) KCBlock doWork;
@property (nonatomic, copy) KCBlock doStudent;
- (void)block_weak_strong {
__weak typeof(self) weakSelf = self;
self.doWork = ^{
__strong typeof(self) strongSelf = weakSelf;
weakSelf.doStudent = ^{
NSLog(@"%@", strongSelf);
};
weakSelf.doStudent();
};
self.doWork();
}
这个方法依然会造成内存泄漏!
解析:
这个方法的问题出在
weakSelf.doStudent = ^{
NSLog(@"doStudent:%@", strongSelf);
};
weakSelf.doStudent();
strongSelf被doStudent进行捕获持有,那么strongSelf的引用计数就会+1;所以在当前代码块执行完毕,strongSelf引用计数-1之后,其引用计数依然不为0;所以无法释放;