前言:block变量捕获
在学习block的变量捕获时,我们知道了block对全局变量不捕获,对局部变量捕获。但讲解block引用的变量都是基本数据类型,那引用对象类型的时候呢? image.png1、block引用外部对象
那下面就MRC环境,我们对三种类型的block来进行分析:
在类MJPerson的delloc方法中加入打印代码来观察person什么时候销毁。
@implementation MJPerson
-(void)dealloc{
[super dealloc];//ARC情况下不需要调用[super dealloc],MRC情况下需要
NSLog(@"person-----dealloc");
}
@end
-
NSGlobalBlock
不捕获外部变量,所以不在讨论范围内 -
NSStackBlock
//情况1: block在栈上 ,不论有没有用__weak修饰对象,不会影响打印结果
- (void)viewDidLoad { //这个括号内都是MyBlock的作用域
[super viewDidLoad];
typedef void(^MyBlock)();
MyBlock block;
{//这个括号是person的作用域
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
person.name = @"Alice";
//使用__weak修饰 用weakPerson代替person,也不会影响打印结果
__weak MJPerson *weakPerson = person;
block = ^{
NSLog(@"person的age为%d",person.age);
NSLog(@"person的name为%@", weakPerson.name);
};
NSLog(@"block的类型是%@",[block class]);
NSLog(@"将要离开person的作用域了");
[person release];//MRC环境下要手动释放
}
//按照局部变量的作用域来说,person离开了这个括号的时候会被释放,但是block还没有被释放
NSLog(@"将要离开block的作用域了");
[block release];//MRC环境下要手动释放
}
//问题:在ARC环境下,person会什么时候释放吗?
//答:在ARC情况下,block会自己copy变成NSMallocBlock类型的block,所以我们研究NSMallocBlock类型的情况就好。往下面看。
=======================================================
打印结果:
BlockTest[2933:174034] block的类型是__NSStackBlock__
BlockTest[2933:174034] 将要离开person的作用域了
BlockTest[2933:174034] person-----dealloc
BlockTest[2933:174034] 将要离开block的作用域了
从上面的结果可以看出,NSStackBlock类型的block引用外部对象时,不论有没有用__weak修饰对象,都不会对其强引用(对象person的引用计数没有加1)。
- NSMallocBlock
//情况2:block在堆上 : 这个里面需要好好分析是 weak还是strong
-(void)viewDidLoad{
[super viewDidLoad];
typedef void(^MyBlock)();
MyBlock block;
{//这个括号是person的作用域
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
person.name = @"Alice";
//注意:case1 和 case2 不能同时存在
//case1:不用__weak 修饰
block = [^{
NSLog(@"person的age为%d",person.age);
NSLog(@"person的name为%@",person.name);
} copy];
//case2:用__weak 修饰 MJPerson
__weak MJPerson *weakPerson = person;
block = [^{
NSLog(@"person的age为%d",weakPerson.age);
NSLog(@"person的name为%@",weakPerson.name);
} copy];
NSLog(@"block的类型是%@",[block class]);
NSLog(@"将要离开person的作用域了");
[person release];//MRC环境下要手动释放
}
//按照局部变量的作用域来说,person离开了这个括号的时候会被释放,但是block还没有被释放
NSLog(@"将要离开block的作用域了");
[block release];//MRC环境下要手动释放
}
=======================================================
case1:不用__weak 修饰
打印结果:
BlockTest[3256:195500] block的类型是__NSMallocBlock__
BlockTest[3256:195500] 将要离开person的作用域了
BlockTest[3256:195500] 将要离开block的作用域了
BlockTest[3256:195500] person-----dealloc
=======================================================
case2:用__weak 修饰 MJPerson
打印结果:
BlockTest[3411:205726] block的类型是__NSMallocBlock__
BlockTest[3411:205726] 将要离开person的作用域了
BlockTest[3411:205726] person-----dealloc
BlockTest[3411:205726] 将要离开block的作用域了
看表面现象总结:NSMallocBlock类型的block引用外部对象时,如果被__weak修饰时,不会强引用(对象person的引用计数不加1),否则被强引用(对象person的引用计数加1)。
2、探究本质
编译一下ViewController.m文件,看源码
//和之前的编译命令不一样来,因为文件了含有__weak,需要运行时环境才可编译,所以加上runtime=ios-8.0.0(其中ios版本不是非要写8.0.0)
//要在环境为ARC下面执行命令哟
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 ViewController.m
引用了MJPerson对象的block底层结构
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
person.name = @"Alice";
//没有用__weak修饰,其实就是默认strong修饰的
block = [^{
NSLog(@"person的age为%d",person.age);
NSLog(@"person的name为%@",person.name);
} copy];
=======================================================
编译后的底层block结构:
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
//注意:这里是__strong类型的person
MJPerson *__strong person;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, MJPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
引用了__weak MJPerson对象的block底层结构
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
person.name = @"Alice";
//weak修饰
__weak MJPerson *weakPerson = person;
block = [^{
NSLog(@"person的age为%d",weakPerson.age);
NSLog(@"person的name为%@",weakPerson.name);
} copy];
=======================================================
编译后的底层block结构:
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
//注意:这里是__weak类型的person
MJPerson *__weak weakPerson;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, MJPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
相比之前学习block捕获auto变量的时候,这里多了一个copy和dispose操作。
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
//注意:下面的copy、dispose是函数指针,分别对应着__ViewController__viewDidLoad_block_copy_0、__ViewController__viewDidLoad_block_dispose_0。
void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};
__ViewController__viewDidLoad_block_copy_0函数;
static void __ViewController__viewDidLoad_block_copy_0(struct
__ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person,
3/*BLOCK_FIELD_IS_OBJECT*/);}
__ViewController__viewDidLoad_block_dispose_0函数:
static void __ViewController__viewDidLoad_block_dispose_0(struct
__ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->person,
3/*BLOCK_FIELD_IS_OBJECT*/);}
_ViewController__viewDidLoad_block_copy_0函数中都有一个_Block_object_assign方法,这个函数会根据auto变量(MJPerson)的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain:计数器加一)或者弱引用
同理,__ViewController__viewDidLoad_block_dispose_0函数中的_Block_object_dispose方法也会在block销毁的时候对auto变量(MJPerson)进行类似(release:计数器减一)释放操作
3 、总结
-
<1>三种类型block是如何访问对象类型的auto变量的?
1、如果block是在数据区,不会访问auto类型对象,不在讨论范围
2、如果block是在栈上,将不会对auto变量产生强引用
3、如果block是在堆上
(1) block被拷贝到堆上时,会调用block内部的copy函数,然后copy函数内部会调用_Block_object_assign函数,然后_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain:计数器加一)或者弱引用
(2)block从堆上移除时候,会调用block内部的dispose函数,然后dispose函数内部会调用_Block_object_dispose函,然后_Block_object_dispose函数会自动释放引用的auto变量(release:计数器减一)
网友评论