这是我参与8月更文挑战的第20天,活动详情查看: 8月更文挑战
block的分类
iOS
中block
分为三类:
NSGlobalBlock
:全局block- 位于全局区;
- 在
block
内部不使用外部变量,或者只是用静态变量和全局变量;
NSMallocBlock
:堆block- 位于堆区;
- 在
block
内部使用局部变量或者OC
属性,并且赋值给强引用;
NSStackBlock
:栈block- 位于栈区;
- 与
NSMallocBlock
一样,可以在内部使用局部变量或者OC
属性。但是不能赋值给强引用或者Copy
修饰的变量;
block的运行
NSGlobalBlock
:全局block,内部不使用外部变量NSMallocBlock
:堆block,内部使用变量a
NSStackBlock
:栈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
;所以无法释放;