链接:https://juejin.im/post/6890071440998498311
前言
在iOS开发中,相信大家在开发中很频繁使用
block
,使用block
来作为参数,属性,回调等等。虽然知道怎么使用block
,但是block
在底层的原理是怎样的,应该还是有的人不是很清楚的,这篇文章就是主要介绍block
的底层原理的。
1. Block的基础
Block
是一个OC
的对象,它封装了一段代码,这段代码可以在任何时候执行。Block
可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。可以嵌套定义,可以定义在方法内部和外部。
1.1 Block种类
在实际使用的Block
种根据内存情况,可以将其分为3种
。
_NSConcreteGlobalBlock
:全局Block。_NSConcreteMallocBlock
:堆Block。_NSConcreteStackBlock
:栈Block(copy之前的)。
通过一个小的demo可以分别打印出来
void (^GlobalBlock)(void) = ^{
NSLog(@"执行GlobalBlock");
};
int a = 10;
void (^MallocBlock)(void) = ^{
NSLog(@"执行MallocBlock- %d",a);
};
GlobalBlock();
MallocBlock();
NSLog(@"我是全局block--%@",GlobalBlock);
NSLog(@"我是堆block--%@",MallocBlock);
NSLog(@"我是栈%@",^{
NSLog(@"执行StackBlock--%d",a);
});
//打印结果===================
执行GlobalBlock
执行MallocBlock- 10
我是全局block--<__NSGlobalBlock__: 0x10c443090>
我是堆block--<__NSMallocBlock__: 0x600003b937b0>
我是栈<__NSStackBlock__: 0x7ffee37bb458>
但是Block
的种类有6种
,另外3种
是系统级别的很少用到。Block
的种类如下:
void * _NSConcreteStackBlock[32] = { 0 };
void * _NSConcreteMallocBlock[32] = { 0 };
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteGlobalBlock[32] = { 0 };
void * _NSConcreteWeakBlockVariable[32] = { 0 };
1.2 Block循环引用
在使用Block
的时候,最容易遇到的就是循环引用
这种错误了。
@property (nonatomic, copy) NSString *name;
@property(nonatomic,copy) void(^testBlock)(void);
self.name = @"jason";
self.testBlock = ^{
NSLog(@"%@",self.name);
};
self.testBlock();
其实在Xcode
上写这段代码的时候也会直接报
Capturing 'self' strongly in this block is likely to lead to a retain cycle
为什么会循环引用呢?因为self持有了block,block持有了self(self.name)
,就形成了self->block->self
这样的闭环,然后导致循环引用。一般情况下,为了避免引起循环引用会加一个__weak
来修饰。
self.name = @"jason";
__weak typeof(self) weakSelf = self;
self.testBlock = ^{
NSLog(@"%@",weakSelf.name);
};
self.testBlock();
此时的weakSelf
是在一张弱引用表
里面的,此时的持有状态是weakSelf持有了self,self持有了block,block持有了weakSelf
,这时候看上去是不是还是一个闭环?但是weakSelf持有self
的时候引用次数并没有处理的就是数量没有增加的,所以此时正常还是会执行到析构函数(delloc)。只是指向了self
而已。如果在testBlock
加一个延迟的然后再打印,此时再打印出来的是weakSelf.name
是一个nil
。
self.name = @"jason";
__weak typeof(self) weakSelf = self;
self.testBlock = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
NSLog(@"%@",weakSelf.name);
});
};
self.testBlock();
因为在延迟的过程中,self
被delloc
之后会被回收,此时的weakSelf
就会被设置为nil
了。为了防止这种情况发生,可以加一个__strong
的修饰
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
此时相当于一个临时的strong
来持有了weakSelf
,在strong
还没被销毁的情况下weakSelf
也不会销毁,只有打印完之后才会销毁。
2.Block的本质
为了方便接下来的介绍,创建一个block.c
文件,通过clang
来查看block
的底层源码分析,其中block.c
的源码如下
#include "stdio.h"
int main(){
void(^block)(void) = ^{
printf("TestJason");
};
block();
return 0;
}
在该文件的目录下,用终端输入命令行,就会在该目录下生成一个block.cpp
的文件
clang -rewrite-objc block.c -o block.cpp
通过block.cpp
文件可以看到部分的源码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteSt