一.Block内部为什么不能修改auto变量
在block内部修改auto变量时,编译器会报错,Variable is not assignable (missing __block type specifier,代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
void(^block)(void) = ^{
// 编译报错Variable is not assignable (missing __block type specifier
a = 20;
};
}
return 0;
}
为什么在block内部无法修改变量a的值呢,通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m命令,我们看下这段代码的c++实现
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block的实现在__main_block_func_0函数中
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_2f53be_mi_0,a);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 变量a定义在 main函数中
int a = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
}
return 0;
}
通过block c++实现,我们可以看到,变量a是定义在 main函数中的,并且在初始化block变量时,传入的是变量啊的值10,并不是a的地址。所以在block中保存的是10的值。 block的实现在__main_block_func_0函数中,int a = __cself->a 只是取出block中的值,只能读写block中的变量啊,是无法修改main函数值定义的auto 变量a的值。从作用域的角度来说,__main_block_func_0函数也无法修改main函数中定义的auto变量
二.Block内部修改变量的几种方式
- static修饰的局部变量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *a; // 保存静态变量a的地址
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block实现,修改main函数中定义的变量a
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//通过block取出变量a的地址,赋值给指针a
int *a = __cself->a; // bound by copy
// 把指针a指向的内存单元的的存储值设置为20
(*a) = 20;
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 定义static变量
static int a = 10;
// 传入变量a的地址&a,block内部保存的是main函数中的a的地址,所以在block中是可以访问到main函数中的变量a
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
}
return 0;
}
可以看到,block中保存的是a的地址,所以可以在__main_block_func_0取出block中a的地址,进而修改局部变量a的值
- 全局变量
全局变量的作用域是全局的,谁都可以访问,所以在任何地方都能修改全局变量的值
- __block修饰auto变量
- __block无法修饰全局变量和静态变量
- 编译器会将__block修饰的变量包装成一个对象
// block oc实现
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = 10;
__block Book *book = [[Book alloc] init];
__block __weak Book *weakBook = book;
void(^block)(void) = ^{
a = 20;
NSLog(@"%d--%@---%@",a,book,weakBook);
};
NSLog(@"%d",a);
NSLog(@"%p---%p",&a,block);
}
return 0;
}
// block c++实现
// __block修饰的 int a
struct __Block_byref_a_0 {
void *__isa; // 说明__Block_byref_a_0是一个对象
__Block_byref_a_0 *__forwarding; // 指向__Block_byref_a_0类型的指针
int __flags;
int __size; // 记录了结构体的大小
int a; //保存变量a的值
};
// __block修饰的 Book book
struct __Block_byref_book_1 {
void *__isa; // 说明__Block_byref_book_1是一个对象
__Block_byref_book_1 *__forwarding; // 指向__Block_byref_book_1类型的指针
int __flags;
int __size; // 记录了结构体的大小
void (*__Block_byref_id_object_copy)(void*, void*); //指向 __Block_byref_id_object_copy_131函数
void (*__Block_byref_id_object_dispose)(void*); // 指向 __Block_byref_id_object_dispose_131函数
Book *__strong book; // 1.指向block外面的book对象 2.__strong对应外面的强指针
};
// __block修饰的 weakBook
struct __Block_byref_weakBook_2 {
void *__isa; // 说明__Block_byref_weakBook_2是一个对象
__Block_byref_weakBook_2 *__forwarding; // 指向__Block_byref_weakBook_2类型的指针,
int __flags;
int __size; // 记录了结构体的大小
void (*__Block_byref_id_object_copy)(void*, void*); //指向 __Block_byref_id_object_copy_131函数
void (*__Block_byref_id_object_dispose)(void*); // 指向 __Block_byref_id_object_dispose_131函数
Book *__weak weakBook; // 1.指向block外面的book对象 2.__weak对应外面的弱指针
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// block结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref 指向结构体a->__forwarding
__Block_byref_book_1 *book; // by ref 指向结构体_book->__forwarding
__Block_byref_weakBook_2 *weakBook; // by ref 指向结构体_weakBook->__forwarding
// block初始化构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_book_1 *_book, __Block_byref_weakBook_2 *_weakBook, int flags=0) : a(_a->__forwarding), book(_book->__forwarding), weakBook(_weakBook->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block 代码块实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 通过block对象取出结构体a
__Block_byref_a_0 *a = __cself->a; // bound by ref
__Block_byref_book_1 *book = __cself->book; // bound by ref
__Block_byref_weakBook_2 *weakBook = __cself->weakBook; // bound by ref
//通过__forwarding,拿到结构体内的成员变量a,修改成员变量a的值,这里修改的是结构体中的成员变量a的值
(a->__forwarding->a) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_769491_mi_0,(a->__forwarding->a),(book->__forwarding->book),(weakBook->__forwarding->weakBook));
}
// 对 a book weakBook进行强引用或者弱引用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->book, (void*)src->book, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->weakBook, (void*)src->weakBook, 8/*BLOCK_FIELD_IS_BYREF*/);
}、
// 对 a book weakBook进行内存管理
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->book, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->weakBook, 8/*BLOCK_FIELD_IS_BYREF*/);
}
// 描述block信息的结构体
static struct __main_block_desc_0 {
size_t reserved; // 保留字段
size_t Block_size; // block结构体的size
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); //copy 指向 __main_block_copy_0函数的指针
void (*dispose)(struct __main_block_impl_0*); // dispose 指向 __main_block_dispose_0函数的指针
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
// main 函数
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 定义了__Block_byref_a_0类型的结构体 a
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
(void*)0, // isa
(__Block_byref_a_0 *)&a, // 将结构体a的地址赋值给__forwarding
0, // flags
sizeof(__Block_byref_a_0),// 传入结构体a的大小
10 //保存10
};
//定义了__Block_byref_book_1类型的结构体 book
__attribute__((__blocks__(byref))) __Block_byref_book_1 book = {
(void*)0, //isa
(__Block_byref_book_1 *)&book, // 将结构体book的地址赋值给__forwarding
33554432, //flags
sizeof(__Block_byref_book_1), //传入结构体__Block_byref_book_1的大小
__Block_byref_id_object_copy_131, // copy函数
__Block_byref_id_object_dispose_131, // dispose函数
((Book *(*)(id, SEL))(void *)objc_msgSend)((id)((Book *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Book"), sel_registerName("alloc")), sel_registerName("init"))}; // 初始化Book对象,赋值给book指针
//定义了__Block_byref_weakBook_2类型的结构体 book
__attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __Block_byref_weakBook_2 weakBook = {
(void*)0, //isa
(__Block_byref_weakBook_2 *)&weakBook, //weakBook结构体的地址赋值给__forwarding
33554432, //flags
sizeof(__Block_byref_weakBook_2), //传入结构体__Block_byref_weakBook_2的大小
__Block_byref_id_object_copy_131, // copy函数
__Block_byref_id_object_dispose_131, // dispose函数
(book.__forwarding->book) // 取出book内部__forwarding指向的对象,赋值给weakBook指针
};
// 定义block,传入的是结构体 a book weakBook 的地址
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_book_1 *)&book, (__Block_byref_weakBook_2 *)&weakBook, 570425344));
}
return 0;
}
1.通过NSLog我们可以看到a的值确实被修改成了20,那么为什么加上__block之后就可以修改a的值呢?
- __block修饰的变量a会包装成一个__Block_byref_a_0类型的结构体a,并且将结构体a的地址保存到block对象中。
- __Block_byref_a_0结构体有一个成员变量a,保存a的值10;有一个__forwarding指针指向__Block_byref_a_0类型的结构体。
- 在block代码块中,通过block拿到__Block_byref_a_0类型的结构体a,通过a->__forwarding->a拿到结构体的成员变量a,将20保存到成员变量a中,此时__Block_byref_a_0结构体里面保存的值已经被修改成为了20。
- 在外面使用变量a的值,都是使用__Block_byref_a_0结构体中保存的a的值。
证明block外面使用的变量a是__Block_byref_a_0内部的成员变量a
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_a_0 {
void *__isa; // 0x1030776c0 8
struct __Block_byref_a_0 *__forwarding;// 8
int __flags; // 4
int __size; //4
int a; //0x1030776d8
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} ;
struct __main_block_impl_0 {
struct __block_impl impl; //0x10307e8a0
struct __main_block_desc_0* Desc;
struct __Block_byref_a_0 *a; // by ref 0x10307e8d0
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = 10;
void(^block)(void) = ^{
a = 20;
};
block();
// 强制类型转换,将block转换成结构体block1
struct __main_block_impl_0 *block1 = (__bridge struct __main_block_impl_0 *)block;
NSLog(@"%d",a);
// 打印变量a的地址 __Block_byref_a_0结构体a的地址
NSLog(@"%p---%p---%p",&a,block1->a,block1);
}
return 0;
}
打印a的地址:0x1030776d8,__Block_byref_a_0的地址0x1030776c0,内存相差24个字节。我们知道结构体的地址就是结构体第一个成员变量的地址,所以__isa的地址是0x1030776c0,指针变量占8个字节,int类型的变量占4个字节,可以计算出成员变量a的地址是 0x1030776d8。也就是说block外面使用的a的地址与__Block_byref_a_0成员变量a的地址相同。经过__block修饰的变量,以后在使用的时候,都是使用__Block_byref_a_0内部的成员变量。
三. __forwarding指针的作用
在上面的源码中,访问a的值都是通过(a->__forwarding->a)访问,为什么要不直接使用a->a来访问,而要通过__forwarding指针间接访问呢?
在前面的文章中提过,在ARC环境下,被强指针引用的栈block,编译器会自动将栈block拷贝到堆中。并且block中的用到的__block变量也会被一起拷贝到堆中,并且被堆上的Block持有。即使block代码块结束,由于堆上的block被强指针引用,所以堆上的block不会被销毁,__block变量也不会销毁。示意图如下:
![](https://img.haomeiwen.com/i754227/d02e38d2ff3f8e55.png)
当编译器将__block变量结构体实例在从栈上被拷贝到堆上时,会将成员变量的__forwarding的值替换为复制目标堆上的__block变量结构体实例的地址,堆中的__forwarding指向自己。通过__forwarding,如果访问的是栈block,会通过__forwarding找到堆中的block,堆block的__forwarding指向自己,从而找到堆中的__block变量a;如果访问的是堆中的block,通过__forwarding指针,也是找到堆中的__block变量a。示意图如下:
复制__block变量后__forwarding指针的变化.pn.png
总结:__forwarding则保证了无论在栈上还是堆上访问的都是同一个__block变量
通过对内存地址的分析,我们也可以__main_block_impl_0 里面的指针是 __Block_byref_a_0里面的__forwarding指针;
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = 10;
void(^block)(void) = ^{
a = 20;
};
block();
// 强制类型转换,将block转换成结构体block1
struct __main_block_impl_0 *block1 = (__bridge struct __main_block_impl_0 *)block;
NSLog(@"%d",a);
// 打印变量a的地址 __Block_byref_a_0结构体a的地址
NSLog(@"%p---%p---%p",&a,block1->a,block1);
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl; //0x100438440 24
struct __main_block_desc_0* Desc; //0x100438440+24 = 0x100438458 8
struct __Block_byref_a_0 *a; // 0x100438458 + 8 = 0x100438460
};
struct __Block_byref_a_0 {
void *__isa; // 8
struct __Block_byref_a_0 *__forwarding;// 8
int __flags; // 4
int __size; //4
int a;
};
block1的地址是0x100438440,__Block_byref_a_0 a的地址是0x100438470。从上面代码的计算过程可以看到__Block_byref_a_0 *a 的地址是0x100438460,并不是0x100438470,而加上__Block_byref_a_0 *a 和void *__isa的16个字节,刚好是0x100438470。
下面是变量val在内存中的变化
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int val = 10;
NSLog(@"val初始化地址-%p", &val);
void(^block)(void) = ^{
val = 20;
NSLog(@"val block地址-%p", &val);
};
block();
NSLog(@"block之后val的地址-%p", &val);
NSLog(@"block---%@",block);
}
return 0;
}
val初始化地址-0x7ffeefbff4b8 --------------------- 栈
val block地址-0x1005186a8 ---------------------- 堆
block之后val的地址-0x1005186a8 -------------------- 堆
block---<NSMallocBlock: 0x100518660> ----------堆
可以看到val变量在被block捕获之后,从栈上到了堆上。这段代码的c++实现(省去部分实现)如下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding; //val在被copy到堆中时,栈上的__forwarding不再指向自己,而是指向堆中的val
int __flags;
int __size;
int val; //
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//__cself->val,cself在堆上,取出堆中的val成员变量
__Block_byref_val_0 *val = __cself->val; // bound by ref
//堆中的__forwarding指针指向自己,val->__forwarding->val取出的是堆中的成员变量val,将堆中的val设置为20
(val->__forwarding->val) = 20;
// Log2.val->__forwarding->val 取出的是堆中的val 20
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_1510ef_mi_1, &(val->__forwarding->val));
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
//Log1. val->__forwarding->val 在栈上
/**val 结构体在栈上 ,__forwarding 指向栈上的val,所以val->__forwarding->val */
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_1510ef_mi_0, &(val.__forwarding->val));
/* 如果栈block被强指针引用,会被copy到堆上,在block被copy到堆上的同时,
会将捕获的变量也同时copy到堆上,所以此时val这个结构体在堆上也有一份
*/
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
//Log3.val->__forwarding->val 堆中
/*
1.val 在栈中
2.__forwarding指向的是堆中的val结构体
3.val.__forwarding->val取出的是堆中的val 20
*/
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_1510ef_mi_2, &(val.__forwarding->val));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_1510ef_mi_3,block);
}
return 0;
}
// 在main.m中重写__main_block_func_0函数
void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSObject *obj = [[NSObject alloc] init]; // 0x100510000 堆中
NSLog(@"obj的对象地址:%p",obj);
NSLog(@"__cself指向的对象:%p",__cself);
NSLog(@"__cself参数的地址:%p",&__cself);
}
2021-04-02 14:16:43.134327+0800 Block1[13163:192597] obj的对象地址:0x1004962c0
2021-04-02 14:16:43.134424+0800 Block1[13163:192597] __cself的对象地址:0x100496230
2021-04-02 14:16:43.134486+0800 Block1[13163:192597] __cself的对象地址:0x7ffeefbff488
可以看到__cself指向堆中的block,修改的是堆中block中的值
![](https://img.haomeiwen.com/i754227/f3882ac7c76a6040.png)
如果栈block被强指针引用,会被copy到堆上,在block被copy到堆上的同时,会将val也同时copy到堆上,所以此时val这个结构体在堆上也有一份
val在被copy到堆中时,栈上的__forwarding不再指向自己,而是指向堆中的val
__forwarding 被设计出来就是为了解决这个问题,如上图所示,栈区的 __forwarding 指向了复制到堆后的 val ,而堆上val指向自己,这也就保证了不论是访问栈还是堆上的val都能获取到正确的值。
网友评论