在上篇文章中有说到ARC环境下,编译器会根据情况自动将栈上的block拷贝到堆上,具体情况以下:
- block作为函数返回值时
- 将block赋值给强指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时。
下面通过代码验证这些情况:
block作为函数返回值和将block赋值给强指针:
typedef void(^BlockDemo)(void);
- (void)viewDidLoad {
[super viewDidLoad];
int a = 1;
int c = 2;
// 访问自动变量
NSLog(@"%@",^{NSLog(@"%d",a);});//打印结果:<__NSStackBlock__: 0x7ffeefac59e0>
// block作为函数/方法返回值
NSLog(@"%@",[[self getBlock] class]); //打印结果:__NSMallocBlock__
BlockDemo strongBlock = ^{NSLog(@"%d",c);};
// 强指针指向block
NSLog(@"%@",[strongBlock class]); //打印结果:__NSMallocBlock__
}
- (BlockDemo)getBlock {
int b = 1;
return ^{
NSLog(@"%d",b);
};
}
从上面代码的打印结果可以看出,单纯的一个访问auto变量的block是NSStackBlock类型,但当这样一个block作为函数返回值,当有强指针指向这样的block时,它是NSMallocBlock类型。
block作为GCD API的方法参数:
- (void)GCDBlcokCaptureObject {
Person *person = nil;
person = [Person new];
person.name = @"jacden";
person.friends = @[@"s",@"2"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"----name:%@-----",person.name);
NSLog(@"------dispatch_after-------");
});
## 情况1和情况2分别和上面代码一起执行
## 情况1
person = [Person new];
person.name = @"lily";
打印结果:
/*
person dealloc
----name:jacden-----
------dispatch_after-------
person dealloc
*/
## 情况2
person.name = @"lily";
打印结果:
/*
----name:lily-----
------dispatch_after-------
person dealloc
*/
}
解释下上面的打印结果,首先1s后block块的代码能执行说明这个block在堆上不是在栈上,能打印person的name值说明block内部强引用了person对象,不然GCDBlcokCaptureObject方法执行完后person、block就会销毁,就会再有打印。
情况1先打印person dealloc,这是因为dispatch_after是异步执行的,1s后才会执行它block内部代码,情况1的代码会先执行,情况1代码执行完方法GCDBlcokCaptureObject也就执行完毕了,因此情况1person指针销毁,新创建的person对象没有强指针指向它,于是销毁调用了dealloc方法。name打印结果为jacden, 是因为block捕获auto变量跟外部变量的类型是一致的,此时外部是Person *指针,block内部会有个Person *指针指向外部变量,情况1是修改了外部person的指向,而block内部的Person *指针仍指向原来的变量。所有打印结果仍然是jacden;1s后block执行完毕,block销毁,不再有指针指向创建的第一个person对象,该对象销毁调用dealloc方法。
情况2只是修改了外部person指针指向的对象的属性值,即所指向的内存中的对象发生了改变,指针指向的地址跟block内部指向的地址是一样的,所有打印结果是最新值;1s后block执行完毕,block销毁,不再有指针指向创建的第一个person对象,该对象销毁调用dealloc方法。
对于情况2,有个迷惑的点,修改person对象的属性值和修改person这个指针是不一样的,需要好好理解下。
下面我们来看下block内部访问对象类型的auto变量的一些情况;
- block在栈上,不会对auto变量产生强引用
- block被拷贝到堆上,内部会调用copy函数,copy函数会调用_Block_object_assign函数,_Block_object_assign函数根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)等作出相应的操作,形成对auto变量的强引用或弱引用
- 当block从堆上移除时,调用block内部dispose函数,dispose函数内部调用_Block_object_dispose函数自动释放引用的auto变量;
函数 | 调用时机 |
---|---|
copy函数 | 栈上的block复制到堆时 |
dispose函数 | 堆上的block被废弃时 |
访问变量用strong修饰时:
- (void)blockCaptureObject {
Person *person = [Person new];
person.name = @"jack";
BlockDemo block = ^{
NSLog(@"%@",person.name);
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
block();
});
}
下面是通过clang转换成c++后的代码
struct __ViewController__blockCaptureObject_block_impl_0 {
struct __block_impl impl;
struct __ViewController__blockCaptureObject_block_desc_0* Desc;
Person *__strong person; ##这里显示是强指针
__ViewController__blockCaptureObject_block_impl_0(void *fp, struct __ViewController__blockCaptureObject_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ViewController__blockCaptureObject_block_func_0(struct __ViewController__blockCaptureObject_block_impl_0 *__cself) {
Person *__strong person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_a592c8_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("name")));
}
static void __ViewController__blockCaptureObject_block_copy_0(struct __ViewController__blockCaptureObject_block_impl_0*dst, struct __ViewController__blockCaptureObject_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __ViewController__blockCaptureObject_block_dispose_0(struct __ViewController__blockCaptureObject_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __ViewController__blockCaptureObject_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__blockCaptureObject_block_impl_0*, struct __ViewController__blockCaptureObject_block_impl_0*);
void (*dispose)(struct __ViewController__blockCaptureObject_block_impl_0*);
} __ViewController__blockCaptureObject_block_desc_0_DATA = { 0, sizeof(struct __ViewController__blockCaptureObject_block_impl_0), __ViewController__blockCaptureObject_block_copy_0, __ViewController__blockCaptureObject_block_dispose_0};
static void _I_ViewController_blockCaptureObject(ViewController * self, SEL _cmd) {
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_a592c8_mi_0);
BlockDemo block = ((void (*)())&__ViewController__blockCaptureObject_block_impl_0((void *)__ViewController__blockCaptureObject_block_func_0, &__ViewController__blockCaptureObject_block_desc_0_DATA, person, 570425344));
dispatch_after(dispatch_time((0ull), (int64_t)(2 * 1000000000ull)), dispatch_get_main_queue(), ((void (*)())&__ViewController__blockCaptureObject_block_impl_1((void *)__ViewController__blockCaptureObject_block_func_1, &__ViewController__blockCaptureObject_block_desc_1_DATA, block, 570425344)));
}
打印结果:
jack
person dealloc
从代码可以看出block内部会有个Person *的强指针指向外部的person,__ViewController__blockCaptureObject_block_desc_0函数相比访问基本数据类型,多了两个成员变量copy函数和dispose函数,在函数的初始化时将__ViewController__blockCaptureObject_block_copy_0函数赋值给了copy函数,将__ViewController__blockCaptureObject_block_dispose_0赋值给了dispose函数。这两个函数就是上面所列举的用来处理person的内存。另外2s后执行block块代码,能打印出来name的值说明block内部强引用着person。block执行完,person执行dealloc方法,说明block不再有强引用着person。
访问变量用weak修饰时:
- (void)blockCaptureObject {
Person *person = [Person new];
person.name = @"jack";
__weak Person *weakPerson = person;
BlockDemo block = ^{
NSLog(@"%@",weakPerson.name);// 打印结果:null
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
block();
});
}
下面是通过clang转换成c++后的代码
struct __ViewController__blockCaptureObject_block_impl_0 {
struct __block_impl impl;
struct __ViewController__blockCaptureObject_block_desc_0* Desc;
Person *__weak weakPerson; ##这里显示是弱指针
__ViewController__blockCaptureObject_block_impl_0(void *fp, struct __ViewController__blockCaptureObject_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ViewController__blockCaptureObject_block_func_0(struct __ViewController__blockCaptureObject_block_impl_0 *__cself) {
Person *__weak weakPerson = __cself->weakPerson; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_b47619_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)weakPerson, sel_registerName("name")));
}
static void __ViewController__blockCaptureObject_block_copy_0(struct __ViewController__blockCaptureObject_block_impl_0*dst, struct __ViewController__blockCaptureObject_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __ViewController__blockCaptureObject_block_dispose_0(struct __ViewController__blockCaptureObject_block_impl_0*src) {_Block_object_dispose((void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __ViewController__blockCaptureObject_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__blockCaptureObject_block_impl_0*, struct __ViewController__blockCaptureObject_block_impl_0*);
void (*dispose)(struct __ViewController__blockCaptureObject_block_impl_0*);
} __ViewController__blockCaptureObject_block_desc_0_DATA = { 0, sizeof(struct __ViewController__blockCaptureObject_block_impl_0), __ViewController__blockCaptureObject_block_copy_0, __ViewController__blockCaptureObject_block_dispose_0};
static void _I_ViewController_blockCaptureObject(ViewController * self, SEL _cmd) {
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_b47619_mi_0);
__attribute__((objc_ownership(weak))) Person *weakPerson = person;
BlockDemo block = ((void (*)())&__ViewController__blockCaptureObject_block_impl_0((void *)__ViewController__blockCaptureObject_block_func_0, &__ViewController__blockCaptureObject_block_desc_0_DATA, weakPerson, 570425344));
dispatch_after(dispatch_time((0ull), (int64_t)(2 * 1000000000ull)), dispatch_get_main_queue(), ((void (*)())&__ViewController__blockCaptureObject_block_impl_1((void *)__ViewController__blockCaptureObject_block_func_1, &__ViewController__blockCaptureObject_block_desc_1_DATA, block, 570425344)));
}
打印结果:
person dealloc
(null)
从代码可以看出block会有个Person *的弱指针指向外部的person,同样__ViewController__blockCaptureObject_block_desc_0函数相比访问基本数据类型,多了两个成员变量copy函数和dispose函数。另外执行block块代码前,person就调用了dealloc方法,打印name的值为(null),说明block内部没有强引用着person,blockCaptureObject方法执行完person就销毁了。
网友评论