目录
__block修饰符
一、为什么在Block内不能修改变量的值
typedef void(^blk)(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
int age = 10;
Person * person = [[Person alloc] init];
blk b=^(){
NSLog(@"age is %d,person is %@",age,person);
//age = 20;//该行代码会报错
//person = [[Person alloc] init];//该行代码会报错
};
b();
}
return 0;
}
通过前面学习我们知道Block会捕获auto变量,其结构体如下。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
Person *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, Person *_person, int flags=0) : age(_age), person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
调用函数如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
Person *person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s1_dbzn9w0x6h121vpvtg1vfcf80000gn_T_main_468f42_mi_0,age,person);
}
可知Block表达式内访问的age和person变量实质是结构体的成员变量。另外在函数__main_block_func_0内是没办法做到访问main函数内的age和person变量的。所以程序会报错。
那么我们要在Block表达式内修改age和person变量要怎么做呢?
一种方法是将age和person变为全局变量,另外一种方法是age和person变量变为静态局部变量使用 static修饰。
还有一种方法就是使用__block修饰符。
二、__block修饰符
typedef void(^blk)(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
__block int age = 10;
blk b=^(){
age = 20;
};
b();
NSLog(@"age = %d",age);
}
return 0;
}
用__block修饰后就可以在Block表达式内修改age变量的值。
执行指令并精简代码:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
typedef void(*blk)(void);
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
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, char * argv[]) {
__Block_byref_age_0 age = {
(void*)0,
(__Block_byref_age_0 *)&age,
0,
sizeof(__Block_byref_age_0),
10
};
blk b=&__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_age_0 *)&age,
570425344));
((__block_impl *)b)->FuncPtr(b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s1_dbzn9w0x6h121vpvtg1vfcf80000gn_T_main_0d7163_mi_1,(age.__forwarding->age));
return 0;
}
我们看到int age = 10;
变成了结构体__Block_byref_age_0 age;
。
查看结构体__Block_byref_age_0
的定义,可以看出有一个isa指针,就是变成了对象。
结构体__main_block_impl_0
捕获的是__Block_byref_age_0 age;
对象的地址。
方法__main_block_func_0
内部的调用就是通过该地址找到对象,在找到对象下面的成员变量age进行修改的。
另外有一个细节需要注意:就是NSLog(@"age = %d",age);
访问的age是age.__forwarding->age
。而不是__Block_byref_age_0 *age;
。
__Block_byref_age_0的示意图
Block的内存管理
当Block在栈上时与访问对象auto变量一样并不会对__block修饰的变量(其本质就是对象)进行内存管理。
当Block被拷贝的堆上时,会调用Block内部的copy函数,这时Block会对捕获的对象进行内存管理,如下图所示。
当Block从堆中移除时,会调用Block内部的dispose函数,这时的内存管理如下图所示。
这里需要说明一下,__block修饰的变量底层会转为对象,Block对其捕获与Block捕获正常的对象auto变量是相同的,但有一点需要注意。
//Person对象可以有__strong __weak 修饰符修饰,来决定Block内部对Person对象的内存管理。
Perosn * person = [[Person alloc] init];
__weak Perosn * person = [[Person alloc] init];
//age变量此处不能指定__strong __weak 所以Block内部对__block int age变量一定是强引用。
__block int age = 10;
//另外还有一种形式可以通过指令转成C++代码看下其底层。
__block __weak Person * person = [[Person alloc] init];
forwarding指针
当__block修饰的变量只在栈上的时候,forwarding指针指向自身。
当__block修饰的变量即在栈上存在,又被copy到堆上时,在栈上的__block变量的forwarding指针指向堆上的__block。
这就是为什么是 (age->__forwarding->age) = 20;
而不是(age->age) = 20;
的原因
行者常至,为者常成!