iOS:Block __block修饰符

33次阅读

共计 3869 个字符,预计需要花费 10 分钟才能阅读完成。

__block 修饰符
上一篇文章中说过,auto 类型的局部变量,可以被 block 捕获,但是不能修改值。
__block 可以解决 block 内部无法修改外部 auto 变量的问题。
__block int age = 10;
void (^myblock)(void) = ^{
NSLog(@”%d”,age);
};
age = 20;
myblock();
用法就是这么简单,这样我们修改 age 为 20 的时候,打印也是 20。
我们看看编译后的代码。
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};

//Block
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;
}
};
在 block 内部多了一个指向__Block_byref_age_0 类型结构体的 age 指针。上面我也帖上了这个__Block_byref_age_0 结构体的结构。我们发现 int 类型的 age 在这个结构体内部了。
那也就是说,__block 修饰的变量,编译器会把它包装成一个对象,然后我们的这个成员变量放到了这个对象的内部。
我们观察一下这个__Block_byref_age_0 内部,这些变量可能有疑惑的也就是这个__forwarding。他是一个指向这个结构体自身的指针。而且我们还可以看出来在打印 age 的时候,是也是通过__forwarding 调用的 age(age->__forwarding->ag),具体为什么要多加这个字段,我们后面再说。
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 这个结构体中也多了两个指针,这是与内存管理有关的函数。
底层分析差不多了,那我们还没说到为什么__block 修饰的属性,在 block 内部可以修改,我们看下面的代码
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};

void (*myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));

(age.__forwarding->age) = 20;
我们创建了__Block_byref_age_0 类型的 age 对象,同时把外部 age 的值也就是 10,传递了进去。然后初始化了 block。
关键是下面的修改 age 的值的时候,直接就是修改的 age 对象里面的 age 属性了,然后打印的时候,也是打印的他。
这个地方其实还是挺抽象的了,也不是很好理解。
怎么前面定义的 age 变量跟后面修改的就不是一个了?
__block int age = 10;
NSLog(@”%p”,&age);
void (^myblock)(void) = ^{
NSLog(@”%d”,age);
};
NSLog(@”%p”,&age);
age = 20;
myblock();
这是最简单的方法,打印出两个 age 的地址,就是不一样的。那我们怎么去判断就是__Block_byref_age_0 里面的 age 呢,大家可以参考下面的做法。
struct __Block_byref_age_0 {
void *__isa;
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};

struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(void);
void (*dispose)(void);
};

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

struct __main_block_impl_0 {
struct __block_impl impl;// 8+4+4+8
struct __main_block_desc_0* Desc;//8
struct __Block_byref_age_0 *age;// 8+8+4+4
};

int main(int argc, const char * argv[]) {
@autoreleasepool {

__block int age = 10;

void (^block)(void) = ^{
age = 20;
NSLog(@”age is %d”, age);
};

struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;

NSLog(@”%p”, &age);
}
return 0;
}
上面的操作就是我们把底层的一些结构拿出来,然后把我们的 block 类型桥接成__main_block_impl_0 类型。然后我们通过 debug 可以拿到这个 blockImpl 的地址,然后通过内存中的地址偏移计算出来内部__Block_byref_age_0 中的 age 地址,看看和打印出来的 age 地址是否一致。
我们拿到 blockImpl 的地址是 0x1005002d0(我测试时候的值,每次都不同)。
__main_block_impl_0 内部第一个属性是一个__block_impl 结构体,然后__block_impl 内部两个指针(一个指针 8 字节)两个 int(一个 int4 字节),共占 24 字节。第二个参数是一个指针,8 字节。age 在第三个参数指向的结构体中,也就是说__Block_byref_age_0 类型的 age 内存地址是 0x1005002d0 偏移 32,也就是 0x100500300。然后__Block_byref_age_0 内部的 age 变量前面,有两个指针两个 int,24 字节,0x100500300 再偏移 24,也就是 0x100500318。跟我们 NSLog(@”%p”, &age); 打印的一致。
所以可以得出,我们修改的这个 age,其实就是底层 age 对象内部的 age 变量。
上面我们留下了一个__forwarding 指针的疑问,我们先不着急解决,先说说 block 类型。
block 的类型
block 有 isa 指针,开始我想通过写了几个类型的 block,用 clang 编译看 cpp 代码,但发现一直是这个样子 impl.isa = &_NSConcreteStackBlock;。所以我就用最直接的打印 [obj class] 的方法。
注意,因为在 ARC 的环境下,编译器给我们做了很多内存相关的工作,所以我在研究 block 类型的过程中切换到了 MRC 环境。
我用过的例子就不写了,下面是一个小总结。
一共有三种 Block
__NSGlobalBlock__ 内存位于数据区__NSStackBlock__ 栈区__NSMallocBlock__ 堆区
具体什么样的 block 对应哪一种类型?
__NSGlobalBlock__:没有访问 auto 变量__NSStackBlock__:访问了 auto 变量__NSMallocBlock__:__NSStackBlock__调用 copy
提示我们在声明一个 block 属性的时候,习惯用 copy 关键字,是为了把栈区的 block 拷贝到堆区,让我们来管理他的生命周期。
ARC 环境下会根据情况自动将栈上的 block 拷贝到堆上。ARC 环境下也用 copy 是为了和 MRC 环境统一,也可以用 strong。
__block 修饰对象类型
当__block 变量在栈上时,不会对指向的变量产生强引用。
当__block 变量 copy 到堆上时,会根据这个变量的修饰符是__strong,__weak,__unsafe_unretained 做出相应的操作形成强引用(retain)或者弱引用。(ARC 会 retain,MRC 不会)。
__forwarding 指针
最后说一下上面的__forwarding 指针问题。

这个图可以很好地诠释这个问题了。
我们的 block 在内存中可能位于栈上,可能在堆上。
使用了这个指针之后,让我们在 block 位于不同内存位置的情况下,访问到相应准确位置的变量。

正文完
 0