Block 的本质是一个OC对象
首先Block是一个OC对象,可以通过打印父类信息得到
void (^block)(void) = ^{
NSLog(@"block --- ");
};
block();
NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
得到打印结果
Block[38376:1019280] block ---
Block[38376:1019280] _NSGlobalBlock_
Block[38376:1019280] __NSGlobalBlock
Block[38376:1019280] NSBlock
Block[38376:1019280] NSObject
由上打印信息可知,Block确实是一个继承于NSObject的OC对象
下面也可通过 Clang 将这段代码转换成 C++代码,探究编译器将这段OC代码转成C++代码
int main(int argc, const char * argv[]) {
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
简化
int main(int argc, const char * argv[]) {
void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
block->FuncPtr;
return 0;
}
编译器将这段OC代码转成以上的C++代码,可以看到Block转成了一个 __main_block_impl_0 这样的一个结构体,并且将__main_block_func_0、&__main_block_desc_0_DATA这两个参数传入,首先先看下__main_block_impl_0这个结构体
struct __main_block_impl_0 {
struct __block_impl impl; // __block_impl 结构体
struct __main_block_desc_0* Desc; // __main_block_desc_0 结构体
// C++构造方法
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; // Block 也有一个 isa 指针,说明 block 是一个OC对象,这里指向了一个_NSConcreteStackBlock类
impl.Flags = flags;
impl.FuncPtr = fp; // 方法地址
Desc = desc;
}
};
这里直接将方法地址赋值给了 __block_impl 这个结构体的FuncPtr ,那么接下来继续看看 __block_impl 这个结构体
struct __block_impl {
void *isa; // isa指针
int Flags;
int Reserved;
void *FuncPtr; // 存放block实现的方法地址
};
Block的调用直接是调用FuncPtr
__main_block_func_0 方法
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8f_y3mvcz2d4_s228xbxgg4ky0h0000gp_T_main_b2a17e_mi_0);
}
__main_block_desc_0 结构体
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)};
以上就是一个简单的 Block 结构
Block 的类型
Block 有三种类型,分别为 _NSGlobalBlock_ 、 _NSStackBlock、 _NSMallocBlock
要探究Block的类型,首先要在 MRC 环境下,将 ARC关闭
在工程中 target-> Build Setting -> Obective-C Automatic Reference Counting 设置为NO
int age = 10;
void (^block1)(void) = ^{
NSLog(@"block1 --- ");
};
void (^block2)(void) = ^{
NSLog(@"block2 --- %d", age);
};
void (^block3)(void) = [^{
NSLog(@"block3 --- %d", age);
} copy];
block1();
block2();
block3();
NSLog(@"%@", [block1 class]);
NSLog(@"%@", [block2 class]);
NSLog(@"%@", [block3 class]);
打印结果:
Block[39097:1064145] block1 ---
Block[39097:1064145] block2 --- 10
Block[39097:1064145] block3 --- 10
Block[39097:1064145] _NSGlobalBlock_
Block[39097:1064145] _NSStackBlock_
Block[39097:1064145] _NSMallocBlock_
block3经过copy操作变成了了 __NSMallocBlock__类型,那下面继续看看这三种类型经过 copy 操作会发生什么,在原来的代码中进行 copy 操作
int age = 10;
void (^block1)(void) = [^{
NSLog(@"block1 --- ");
} copy];
void (^block2)(void) = [^{
NSLog(@"block2 --- %d", age);
} copy];
void (^block3)(void) = [[^{
NSLog(@"block3 --- %d", age);
} copy] copy];
block1();
block2();
block3();
NSLog(@"%@", [block1 class]);
NSLog(@"%@", [block2 class]);
NSLog(@"%@", [block3 class]);
Block[39644:1093505] block1 ---
Block[39644:1093505] block2 --- 10
Block[39644:1093505] block3 --- 10
Block[39644:1093505] _NSGlobalBlock_
Block[39644:1093505] _NSMallocBlock_
Block[39644:1093505] _NSMallocBlock_
对比上一次的打印结果可以总结出
| 类型 | 经过 copy 操作 |
|---|---|
| _NSGlobalBlock_ | _NSGlobalBlock_ |
| _NSStackBlock_ | _NSMallocBlock_ |
| _NSMallocBlock_ | _NSMallocBlock_ |
如果在 ARC 环境下,__NSStackBlock__ 系统会根据情况自动将栈上的Block 复制到堆上
block 作为函数返回值时(Masonry)
将 block 赋值给 __strong 指针时
block 作为 Cocoa API 中方法名含有 usingBlock 的方法参数时
block 作为 GCD API 的方法参数时
__weak 修饰 auto 变量
话不多说,直接上代码
typedef void(^YXCBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
YXCBlock block;
{
YXCPerson *person = [YXCPerson new];
person.age = 10;
__weak YXCPerson *weakPerson = person;
block = ^{
NSLog(@"----%d", weakPerson.age);
};
}
block();
NSLog(@"%@", [block class]);
}
return 0;
}
输出结果
Block[10400:227395] -[YXCPerson dealloc]
Block[10400:227395] ----0
Block[10400:227395] _NSMallocBlock_
转换C++代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
YXCPerson *__weak weakPerson; // 拥有一个 __weak 的 person 对象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, YXCPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
YXCPerson *__weak weakPerson = __cself->weakPerson; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8f_y3mvcz2d4_s228xbxgg4ky0h0000gp_T_main_8f3cfc_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)weakPerson, sel_registerName("age")));
}
// 通过调用这个函数,将block进行copy操作,_Block_object_assign 会根据 auto 变量的修饰符做出相应的操作,形成强/弱引用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
// 调用这个函数,自动释放 block 中引用的 auto 变量
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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, const char * argv[]) {
{
__AtAutoreleasePool __autoreleasepool;
YXCBlock block;
{
YXCPerson *person = ((YXCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YXCPerson"), sel_registerName("new"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 10);
__attribute__((objc_ownership(weak))) YXCPerson *weakPerson = person;
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakPerson, 570425344));
}
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8f_y3mvcz2d4_s228xbxgg4ky0h0000gp_T_main_8f3cfc_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block, sel_registerName("class")));
}
return 0;
}
block在定义的时候默认是一个 __strong 类型的对象,而在内部引用了 auto 变量,所以在 ARC 环境下会经过 copy操作,由本来的 __NSStackBlock__类型变成__NSMallocBlock__类型的block,引用的auto变量person,经过 __weak 修饰引用到block中,_Block_object_assign函数对 person 产生弱引用,所以person出了作用域就被释放了。
下面不经过 __weak 修饰
typedef void(^YXCBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
YXCBlock block;
{
auto YXCPerson *person = [YXCPerson new];
person.age = 10;
block = ^{
NSLog(@"----%d", person.age);
};
}
block();
NSLog(@"%@", [block class]);
}
return 0;
}
打印结果
Block[10464:231221] ----10
Block[10464:231221] _NSMallocBlock_
Block[10464:231221] -[YXCPerson dealloc]
此时的_Block_object_assign函数对 person 产生强引用,只有等block被释放了,才会调用block内部的 _Block_object_dispose 释放引用的 person 对象。
__block 修饰 auto 变量
在日常使用block中,有需要在block中修改外面auto变量,众所周知,block是不可以直接修改外面的auto变量,这时候需要用到 __block
__block int age = 20;
void (^block)(void) = ^{
age = 50;
};
block();
NSLog(@"age = %d, block的类型:%@", age, [block class]);
打印结果:
Block[10665:243061] age = 50, block的类型:NSMallocBlock
接下来,转换成 C++代码,看看 __block做了什么操作
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // 存放age
__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中没有修改 auto 变量,多了一个 __Block_byref_age_0 类型的结构体,在这里将外面 auto变量
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
下面看 __block int age = 20; 这句代码转换之后的C++代码
/*
0 赋值给 age 的 __isa
age本身的地址赋值给 age 的 __forwarding
0 赋值给 age 的 __flags
sizeof(__Block_byref_age_0) 将age所需要的内存的大小 赋值给 age 的 __size
0 赋值给 age 的 age int变量
*/
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 20};
紧接着看block里面的 age = 50;
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 拿到 age
__Block_byref_age_0 *age = __cself->age; // bound by ref
// 通过 age 的 __forwarding 将 50 赋值给 int age 变量
(age->__forwarding->age) = 50;
}
可以得出一个结论:编译器会将 __block 修饰的变量包装成一个对象,__block 可以用于解决无法在block内部修改值的问题,而且 __block 不能修饰全局变量、静态(static)变量。










网友评论