对象类型的auto变量
在 block
内部访问了对象类型的 auto
变量。
栈
如果 block
是在栈上,将不会对 auto
变量产生强引用。
堆
如果 block
被拷贝到堆上,会调用 block
内部的 copy
函数。
copy
函数内部会调用 _Block_object_assign
函数,然后根据 auto
变量的修饰符(__strong
、__weak
、__unsafe_unretained
)做出相应的操作,形成强引用(retain
)或者弱引用。
如果 block
从堆上移除,会调用 block
内部的 dispose
函数。
dispose
函数内部会调用 _Block_object_dispose
函数,然后自动释放引用的 auto
变量(release
)。
测试
在 block
中引用对象类型的 auto
变量:
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person new];
person.age = 10;
Block block = ^{
NSLog(@"%d", person.age);
};
block();
}
return 0;
}
转换后的 C++
代码:
typedef void (*Block)(void);
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__strong person; // 对Person对象产生强引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock; // 不一定准,要以类对象为准[block class]
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Person *__strong person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_25_pb1crnzn29gcfxnjh75vhb900000gn_T_main_07f896_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
// block被拷贝到堆上会执行copy,引用计数+1
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
// block从堆上被移除会执行dispose,引用计数-1
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
// 捕获对象类型变量,才会有copy/dispose,用于内存管理
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 10);
Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
如果将 block
中引用的对象,用 __weak
修饰:
Person *person = [Person new];
person.age = 10;
__weak Person *weakPerson = person;
Block block = ^{
NSLog(@"%d", weakPerson.age);
};
block();
最终转换后的代码,捕获的变量也是用 __weak
修饰,说明 block
没有对 Person
对象产生强引用(引用计数+1):
Person *__weak weakPerson;
block内部修改atuo变量值
__block
可以用于解决 block
内部无法修改 auto
变量值的问题。
__block
不能修饰全局变量
、静态变量
(static
)。
测试
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int no = 10;
__block int age = 10;
Block block = ^{
// no = 20; // 报错
age = 20;
NSLog(@"%d %d", age, no); // 20 10
};
block();
}
return 0;
}
编译器会将 __block
变量包装成一个对象:
// int age包装后的对象
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
// block结构体
struct __main_block_impl_0 {
// ...
__Block_byref_age_0 *age; // 对象引用
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// __block int age = 10; 会转换成下面代码
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {
(void*)0,
(__Block_byref_age_0 *)&age, // __forwarding指向结构体自己
0,
sizeof(__Block_byref_age_0),
10};
// ...
}
return 0;
}
循环引用问题
block
内部强引用持有对象,对象又强引用持有 block
,就会形成循环引用。
用 __weak
、__unsafe_unretained
解决循环引用:
typedef void (^Block)(void);
@interface ViewController ()
@property (nonatomic, copy) Block block; // self 引用了 block
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// ARC可用
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%p", weakSelf); // block引用了self,但是用了__weak修饰,就是弱引用self
};
// MRC/ARC可用
__unsafe_unretained id weakSelf = self;
self.block = ^{
NSLog(@"%p", weakSelf);
};
}
@end
用 __block
解决,必须要调用 block
,在内部释放强引用的对象:
// MRC/ARC可用
__block id weakSelf = self;
self.block = ^{
NSLog(@"%p", weakSelf);
weakSelf = nil;
};
self.block();
block weak
__weak
只能修饰对象类型的变量,__block
则不限制变量的类型。所以又想在 block
内部修改变量的值,又想防止强引用,则可以使用 __block __weak
修饰对象类型的变量。