最近读书,关于block的底层实现,有以下思考和总结
- c++部分的相关步骤分析写在代码注释
一、block是什么
1.首先写一个简单的block
#import <stdio.h>
int main(void) {
void (^block)(void) = ^{
printf("hello world!");
};
block();
return 0;
}
2.将main.m
编译后 clang -rewite-objc main.m
生成 .cpp
文件
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;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
/**
3.参数fp即传入的参数__main_block_func_0函数的地址,赋值给结构体实例 impl 的属性 FuncPtr
4.参数desc即传入的 &__main_block_desc_0_DATA结构体取地址赋值给了结构体指针 Desc
5.结构体实例 impl 的 isa 指针存放了 _NSConcreteStackBlock类的地址
6._NSConcreteStackBlock是在将block作为OC对象处理时,该类的信息放置于_NSConcreteStackBlock 中,
由此可见,block的实质是OC对象
*/
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("hello world!");
}
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(void) {
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
/**
对应代码
void (^block)(void) = ^{
printf("hello world!");
};
1.等号左边 void (*block)(void)是一个无参无返的函数指针(是一个指针,指向函数)
2.等号右边 __main_block_impl_0 首先,c++结构体包含自己的属性,构造方法,成员方法,
所以 &__main_block_impl_0()是对结构体构造函数取地址,
函数的参数是 (void *)__main_block_func_0 和 &__main_block_desc_0_DATA
*/
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
/**
对应代码 block();
7.前半部分((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)
访问结构体 __block_impl自己的成员变量FuncPtr,
FuncPtr在步骤3被赋值了 __main_block_func_0(block执行代码块)函数的地址
8.后半部分((__block_impl *)block),将block自身作为参数传递
*/
return 0;
}
3.引入变量
int main(void) {
int a = 10;
void (^block)(void) = ^{
printf("%d\n",a);
};
block();
return 0;
}
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) {
/**
2.在c++中 a(_a) 表示 _a 这个形参赋值给 a 这个实参
*/
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
/**
3.定义了一个新的变量,接收结构体成员变量a的值,__cself->a 表示结构体访问自己的属性。
*/
printf("%d\n",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(void) {
int a = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
/**
1.将变量a的值传入,并赋值给了结构体成员变量
*/
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
4.__block变量
int main(void) {
__block int a = 10;
void (^block)(void) = ^{
a += 10;
printf("%d\n",a);
};
block();
return 0;
}
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding; //由步骤2得出指向的结构体本身
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
/**
4.将传入的结构体指针的成员变量__forwarding的值(地址),赋值给当前结构体成员变量结构体指针a,保存了__block变量地址
*/
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_a_0 *a = __cself->a; // bound by ref
/**
5.访问结构体成员变量a,赋值给新的结构体指针
*/
(a->__forwarding->a) += 10;
/**
6.a->__forwarding首先找到结构体指针的指向,a->__forwarding->a获取到有效成员变量a的值并进行修改
*/
printf("%d\n",(a->__forwarding->a));
}
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*/);}
/**
7.栈copy到堆时调用,因为结构体成员变量包含所截获的变量或者__block变量结构体指针,所以copy的时候也一起copy了
*/
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
/**
8.堆上的block废弃时调用
*/
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(void) {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
/**
1.变量a不再单纯的是一个int类型,变成了__Block_byref_a_0结构体类型,并分别传入的参数进行赋值
2.第二个参数(__Block_byref_a_0 *)&a,结构体指针,值是a的地址,a又是结构体__Block_byref_a_0,
所以__Block_byref_a_0中的结构体指针__forwarding指向自身
3.C++中,__attribute__ 表示可以设置函数属性。
理解一下字面意思大概知道内部发生了什么
byref 是把内存地址告诉程序,所以改变的直接就是内存中的数值。按地址传递参数使过程用变量的内存地址去访问实际变量的内容。结果,将变量传递给过程时,通过过程可永远改变变量值。
byval 是把内存数值的拷贝给程序,所以改变的只是拷贝,内存原来的值是不会改变的。按值传递参数时,传递的只是变量的副本。如果过程改变了这个值,则所作变动只影响副本而不会影响变量本身。
*/
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
📍结论:
1.block是函数指针,指向的是block代码块编译时生成的函数地址。执行block相当于找到指向block的指针。
2.block对象是结构体
(1)有isa指针(impl->isa)指向自己的类(_NSConcreteStackBlock)
(2)有desc结构体描述block的信息
(3)__forwarding((__Block_byref_a_0 *)a -> __forwarding)指向自己或堆上自己的地址
(4)当block对象截获变量,变量也会出现在block结构体中(int a)
(5)指向block代码块的函数指针(impl.FuncPtr = fp)。
(6)block结构体的构造函数的参数,包括函数指针,描述block的结构体,自动截获的变量(全局变量不用截获),引用到的__block变量。(__block对象也会转变成结构体)
3.block代码块在编译的时候会生成一个函数,函数的参数是block对象结构体指针。执行block,相当于通过block的地址找到__block变量结构体指针,再找到变量值,进行操作。
二、block的类
1.block在编译中,会被当成结构体处理,block实际结构
structBlock_literal_1{
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
intflags;
intreserved;
void(*invoke)(void *,...); //block执行时调用的函数指针,block定义时内部的执行代码
structBlock_descriptor_1{
unsigned long intreserved; // NULL
unsigned long intsize; // sizeof(struct Block_literal_1)
// optional helper functions
void(*copy_helper)(void *dst, void *src); // IFF (1<<25)
void(*dispose_helper)(void *src); // IFF (1<<25)
// required ABI.2010.3.16
const char *signature; // IFF (1<<30)
} *descriptor;
// imported variables
};
2.block的类有三种
block的类 | 设置对象的存储域 |
---|---|
_NSConcreteGlobalBlock | 程序的数据区域(.data区) |
_NSConcreteStackBlock | 栈 |
_NSConcreteMallocBlock | 堆 |
(1)_NSConcreteGlobalBlock
#import <Foundation/Foundation.h>
void (^block)(void) = ^{
};
int main(void) {
void (^block1)(void) = ^{
};
NSLog(@"%s %p",object_getClassName(block),block);
NSLog(@"%s %p",object_getClassName(block1),block1);
return 0;
}
//输出
//__NSGlobalBlock__ 0x100001058
//__NSGlobalBlock__ 0x100001098
(2)_NSConcreteStackBlock
当block引入变量,变量内存地址会发生如下变化
#import <Foundation/Foundation.h>
int main(void) {
int a = 10;
void (^block)(void) = ^{
printf("%d\n",a);
};
NSLog(@"%s %p",object_getClassName(block),block);
NSLog(@"%@",^{
printf("%d\n",a);
});
return 0;
}
//输出
//__NSMallocBlock__ 0x100443c80
//<__NSStackBlock__: 0x7fff5fbff6b0>
(3)_NSConcreteMallocBlock
从上一步可以看出,block在被赋值后,从栈来到了堆,这段代码是从栈copy到堆的过程
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
...
aBlock = (struct Block_layout *)arg;
...
// Its a stack block. Make a copy.
if (!isGC) {
// 申请block的堆内存
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 拷贝栈中block到刚申请的堆内存中
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 改变isa指向_NSConcreteMallocBlock,即堆block类型
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
else {
...
}
}
- 看到这里或许会有疑问,为什么不直接分配在堆区域上?
这里引入我读到的一篇博客的解释
block是默认分配在栈上的唯一OC对象,因为编译器更倾向于在栈上分配空间,因为执行效率较高,但是只能是在已知实际大小的情况下去分配,只有简单的值(比如指针)可以被分配在栈上,block的大小是确定的,你创建了一个给定的block就不能修改, 在整个执行过程中block不变, 它是需要快速执行的代码段,因此它是栈的最佳候选。
📍结论:
1.定义在函数外的block和定义在函数内部且没有捕获自动变量的block是全局block。
2.ARC下并且有变量捕获的情况下,对block自动执行了copy,将block由栈---->copy---->堆
3.copy的过程中,主要实现,通过memmove函数将block内容进行copy,并且将 isa 指针指向了_NSConcreteMallocBlock
三、block的copy
1.自身的copy
(1)栈Block
栈block拷贝复制了内容,重置了isa指针指向,重置了flags参数
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
(2)堆Block
改变引用计数
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
(3)全局Block
直接返回了传入的block
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
2.__block变量的copy
(1)__block修饰的基本数据类型变量
会生成__Block_byref_a_0
结构体,__block将基本数据类型包装成对象,并且只在最初block拷贝时复制一次,后面的拷贝只会增加这个捕获变量的引用计数。
(2)没有用__block修饰的对象变量
NSObject *obj = [[NSObject alloc] init];
void(^block)(void) = ^{
NSLog(@"%@",obj);
};
block();
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *obj;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSObject *obj = __cself->obj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_jctrsbb11gb_vzv3gzmszy1h0000gn_T_main_8ef076_mi_0,obj);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 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(void) {
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
并没有生成__Block_byref_a_0
结构体,在_Block_object_assign
中对应的判断代码:
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
_Block_retain_object(object);
_Block_assign((void *)object, destAddr);
}
通过以上代码可以看出,对对象进行了retain,操作了对象的引用计数
(3)用__block修饰的对象变量
__block NSObject *obj = [[NSObject alloc] init];
void(^block)(void) = ^{
NSLog(@"%@",obj);
};
block();
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_obj_0 *obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__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_obj_0 *obj = __cself->obj; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_jctrsbb11gb_vzv3gzmszy1h0000gn_T_main_ae30ca_mi_0,(obj->__forwarding->obj));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 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(void) {
__attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
生成了__Block_byref_obj_0
结构体,并且结构体中多了两个成员函数,void (*__Block_byref_id_object_copy)(void*, void*);
和 void (*__Block_byref_id_object_dispose)(void*);
用来实现对对象的内存管理。copy/dispose可以理解为retain(copy)/release
四、block的循环引用
#import "Person.h"
typedef void(^block)(void);
@implementation Person
{
block _blk;
}
-(void)test
{
_blk = ^{
NSLog(@"%@",self); //⚠️warning - retainCycle
};
}
@end
这段代码会报warning⚠️,因为造成循环引用retainCycle
解决
1.__weak一端变为弱引用(MRC下无效)
__weak typeof(self)wself = self;
_blk = ^{
NSLog(@"%@",wself);
};
2.引入__block变量(ARC下无效)
当block由栈copy到堆,若block使用的变量为附有__block说明符的id类型或对象类型的自动变量,不会被retain
这段代码在ARC下不会走dealloc方法
__block id obj = self;
_blk = ^{
NSLog(@"%@",obj);
};
以上是我个人分析,有不合理的地方,欢迎指正