一. 查看block内部实现
1.编写block代码
void (^DemoBlock)(int, int) = ^(int a, int b){ NSLog(@"%d",a+b); }; DemoBlock(1,3); 输出:2019-01-14 13:01:47.104 iOSWorld[1324:103701] 4
- 使用
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc BlockViewController.m
命令查看c++源码,搜索DemoBlock.void (*DemoBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *, int, int))((__block_impl *)DemoBlock)->FuncPtr)((__block_impl *)DemoBlock, 1, 3);
简化:
((void (*)(int, int))
是一个函数指针,也就是把后面的代码强转为一个函数指针.删减后得到(从下往上看)struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; static struct __main_block_desc_0 { size_t reserved;//为0 size_t Block_size;//block的大小 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; //__main_block_impl_0和结构体同名, 是一个构造函数, 返回结构体对象 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b){ NSLog(a+b); } //定义DemoBlock,&__main_block_impl_0是一个函数,有两个参数,第一个为包装的方法,第二个为描述信息 void (*DemoBlock)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)); //执行DemoBlock DemoBlock->FuncPtr(DemoBlock, 1, 3);
代码解释:
1.调用__main_block_impl_0()函数
- 参数1为__main_block_func_0,也就是我们block里面要做的事情,也就是匿名函数 --> NSLog(@"%d",a+b);
- 参数2为__main_block_desc_0_DATA,__main_block_desc_0_DATA是一个struct __main_block_desc_0的结构体,结构体参数reserved=0,结构体参数Block_size=sizeof(struct __main_block_impl_0);
- 调用后会来到__main_block_impl_0这个结构体的构造函数,将内部impl结构体设置impl.isa = &_NSConcreteStackBlock; impl.Flags = flags;impl.FuncPtr = fp;将内部__main_block_desc_0结构体的Desc = desc;
所以:block是封装了函数调用以及函数调用环境的对象
3.分析(注意!!!!!标注的代码)
impl.isa = &_NSConcreteStackBlock;
block有一个isa指针,说明block也是一个OC对象.impl.FuncPtr = fp
记录一下block里面的函数Desc = desc;
记录该block的描述
4.拓展
- (void)test { void(^DemoBlock)(void) = ^{ }; NSLog(@"%@",[DemoBlock class]); NSLog(@"%@",[[DemoBlock class] superclass]); NSLog(@"%@",[[[DemoBlock class] superclass] superclass]); NSLog(@"%@",[[[[DemoBlock class] superclass] superclass] superclass]); } 输出:2019-01-14 16:16:15.953 iOSWorld[1542:128655] __NSGlobalBlock__ 2019-01-14 16:16:15.953 iOSWorld[1542:128655] __NSGlobalBlock 2019-01-14 16:16:15.954 iOSWorld[1542:128655] NSBlock 2019-01-14 16:16:15.954 iOSWorld[1542:128655] NSObject
block有superclass,说明就是OC对象.
二.block变量捕获
- 对基本数据类型的局部变量截获其值.
- 对对象类型的局部变量连同所有权修饰符一起截获,如果以strong修饰,捕获的时候底层就retain一次,如果是weak修饰的,就不retain
self也是局部变量,所以会捕获self
- 以指针形式截获静态局部变量
虽然static修饰的局部变量也是永驻内存,但因为它是局部的,不像全局变量一样谁都可以访问,所以捕获它的指针才能找到它.
- 不截获全局变量、静态全局变量
2.1 block捕获局部变量
demo1
- (void)test { int a = 10; void(^DemoBlock)(void) = ^{ NSLog(@"%d", a); }; a = 20; DemoBlock(); } 输出:2019-01-14 15:00:38.339 iOSWorld[1446:119920] 10
解释:定义完
a=10
后,编译器开始编译DemoBlock,这时候DemoBlock内部已经捕获了a的值.
继续执行a=20
,继续执行DemoBlock()
会打印出已经捕获的a=10
,所以打印为10.
总结: 对于局部变量,一出作用域就会被销毁了,所以block会及时捕获局部变量并copy一份值
到block内部.
2.2 block捕获全局变量
demo2
int a = 10; - (void)test { void(^DemoBlock)(void) = ^{ NSLog(@"%d", a); }; a = 20; DemoBlock(); } 输出:2019-01-14 15:26:38.208 iOSWorld[1478:122759] 20
解释:
int a = 10
,接下来编译DemoBlock,继续执行a=20
,继续执行DemoBlock()
的时候会调用NSLog(@"%d", a)
,因为a是全局变量,这时候只需要拿到a打印就行了.
总结: 对于全局变量,即使出了作用域也不会被销毁,所以block不会捕获全局变量,啥时候用啥时候取就行.下划线_age其实是self->age,所以会先捕获self
三.3种block的内存存放区域
block分类
_NSConcreteStackBlock:在栈上创建的Block对象
_NSConcreteMallocBlock:在堆上创建的Block对象
_NSConcreteGlobalBlock:全局数据区的Block对象(data区)
- 没有捕获局部变量的block为GlobalBlock
- 捕获了局部变量但是没有进行
block复制
则为StackBlock---栈block容易出错,因为有可能被销毁了
- 其他的block基本都为MallocBlock(堆block)
block复制
block从栈复制到堆的时候,会调用_Block_object_assign()
强引用
block在堆上销毁的时候,会调用_Block_object_dispose()
release引用的对象在ARC有效时,大多数情况下编译器会进行判断,自动生成将Block从栈上复制到堆上的代码,以下几种情况栈上的Block会自动复制到堆上:
- 调用Block的copy方法
globalBlock copy仍为globalBlock,stackBlock copy后为mallocBlock,mallocBlock copy后引用计数+1
- 将Block作为函数返回值时
- 将Block赋值给__strong修饰的变量时
- 向Cocoa框架含有usingBlock的方法 或者 GCD的API传递Block参数时
所以如果不怕被销毁,比如该block当函数用一次,那么block不一定非得用copy.
__block可以解除循环引用
四.block修改变量
???为什么不可以在block内部修改局部变量
因为:局部变量出了大括号就会销毁,有可能你修改的时候它已经是nil了.
但是:我们可以使用__block修饰局部变量.
__block int a = 10;
void (^DemoBlock)(void) = ^(){
a = 20;
NSLog(@"%d",a);
};
DemoBlock()
输出:---------> 20
- 使用
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc main.m
命令查看c++源码,搜索DemoBlock.
void (*DemoBlock)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
- 发现__main_block_impl_0实现里面多了一个__Block_byref_a_0开头的变量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
}
- 看看__Block_byref_a_0,发现它里面有个a,还有个自己类型的__forwarding-->
__Block_byref_a_0 *__forwarding
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
4.看看__main_block_func_0这个参数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//创建了一个__Block_byref_a_0类型的和我们定义一样的同名变量a
__Block_byref_a_0 *a = __cself->a;
//将新a的__forwarding里面的a设置为20
(a->__forwarding->a) = 20;
//打印新a的__forwarding里面的a
NSLog(a->__forwarding->a);
}
5.将自己(__Block_byref_a_0
)的地址值传到自己的第二个参数__forwarding
里面,也就是说__forwarding
指向的还是自己
__Block_byref_a_0 *__forwarding = (__Block_byref_a_0 *)&a
__forwarding
存在的意义:不论在栈上还是堆上,都可以顺利的访问同一个__block变量.
总结:__block原理
编译器会将__block变量包装成一个对象,并把局部变量赋值给该对象,这样就能修改对象的变量
你以为你修改的还是那个局部变量,殊不知已经是__block自己生成的对象啦
拓展:
__block __weak Person *p = [Person new]
上面代码:生成一个自己的对象block_p
包裹了p
,
而且block
强引用了block_p
,但是因为有weak
的存在,block_p
对p
是弱引用的.
五.block的内存管理
- stackBlock对所有的局部变量都是弱引用
- mallocBlock :
1.对__block或者__Strong使用_Block_object_assign()进行强引用.
2.对__weak使用_Block_object_assign()进行弱引用.
当block移除时,使用_Block_object_dispose()来释放局部变量.
六.解决循环引用的新方法
将在Block内要使用到的对象(一般为self对象),以Block参数的形式传入,Block就不会捕获该对象,而将其作为参数使用,其生命周期系统的栈自动管理,不造成内存泄露。
即原来使用__weak的写法:
__weak typeof(self) weakSelf = self;
self.blk = ^{
__strong typeof(self) strongSelf = weakSelf;
NSLog(@"Use Property:%@", strongSelf.name);
//……
};
self.blk();
改为Block传参写法后:
self.blk = ^(UIViewController *vc) {
NSLog(@"Use Property:%@", vc.name);
};
self.blk(self);
__weak typeof(self) weakSelf = self;
__weak UIViewController *weakSelf = self;