block 其实就是一个结构体+函数指针,其赋值就是传递block对象(结构体);
下面代码我们将其转为c的源码,clang我用的是这段命令:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk XXXX.m
代码一:
void(^myBlock)(void) = ^() {
printf("ss");
};
myBlock();
clang源码:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __ClangProxy__staff_block_impl_0 {
struct __block_impl impl;
struct __ClangProxy__staff_block_desc_0* Desc;
__ClangProxy__staff_block_impl_0(void *fp, struct __ClangProxy__staff_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ClangProxy__staff_block_func_0(struct __ClangProxy__staff_block_impl_0 *__cself) {
printf("ss");
}
static struct __ClangProxy__staff_block_desc_0 {
size_t reserved;
size_t Block_size;
} __ClangProxy__staff_block_desc_0_DATA = { 0, sizeof(struct __ClangProxy__staff_block_impl_0)};
调用部分源码:
void(*myBlock)(void) = ((void (*)())&__ClangProxy__staff_block_impl_0((void *)__ClangProxy__staff_block_func_0, &__ClangProxy__staff_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
观察上面的源码,能发现struct __block_impl
结构体,
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
这个结构体包含有isa指针,根据我们对OC对象的理解,这个结构体类似于元类对象,
其中flags标志暂时没看出有什么用,只知道在构造函数里初始化为0,reserved则是保留参数,
void *FuncPtr
这个就是block的实现内容了,这个函数指针指向了对应block的实现函数;
当然不是完整部分,一个完整的block不仅应该包含实现函数,同时还要有其引用的外部变量,还有对自身的描述。
再来看看struct __ClangProxy__staff_block_impl_0
,
struct __ClangProxy__staff_block_impl_0 {
struct __block_impl impl;
struct __ClangProxy__staff_block_desc_0* Desc;
__ClangProxy__staff_block_impl_0(void *fp, struct __ClangProxy__staff_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
这个结构体的命名也比较有趣,是类名+函数/方法名+block_impl+数字(第几个block),这个结构体中有两个成员变量,一个构造函数(这里应该是C++用法),我们先看第一个成员变量struct __block_impl impl
,这个就是block的实现部分,其中包含函数指针上面提到过了;其次我们再看第二个成员struct __ClangProxy__staff_block_desc_0* Desc
:
static struct __ClangProxy__staff_block_desc_0 {
size_t reserved;
size_t Block_size;
} __ClangProxy__staff_block_desc_0_DATA = { 0, sizeof(struct __ClangProxy__staff_block_impl_0)};
其中就一个Block_size,方便block初始化,这里已经初始化了一个值:__ClangProxy__staff_block_desc_0_DATA
,取的是struct __ClangProxy__staff_block_impl_0
的size。
再看构造函数,其实这个比较简单就是传入参数(void *fp, struct __ClangProxy__staff_block_desc_0 *desc, int flags=0)
,第一个是block实现的函数指针,第二个就是block描述,第三个在这里固定0(实际运行可能不是0,暂不讨论),
到这里基本就看完了block的结构了,下面看
block调用部分1:
void(*myBlock)(void) = (
(void (*)()) &__ClangProxy__staff_block_impl_0(
(void *)__ClangProxy__staff_block_func_0,
&__ClangProxy__staff_block_desc_0_DATA
)
);
上面代码我简单的做了下换行,方便观察;
-
首先就是
void(*myBlock)(void) = (void(*)()) &__ClangProxy__staff_block_impl_0(...)
这个比较好理解,就是将右边的__ClangProxy__staff_block_impl_0
构造函数创建的结构体通过void(*)()
获取指针,将指针赋值给myBlock。 -
后面就是传参了,分别是block的实现函数指针
__ClangProxy__staff_block_func_0
还有block描述
block调用部分2:
/*第一行*/((void (*)(__block_impl *))
/*第二行*/((__block_impl *)myBlock)->FuncPtr)
/*第三行*/((__block_impl *)myBlock);
这里从第二行myBlock开始看,(__block_impl *)myBlock
将其显示转换为结构体struct __block_impl *
类型,这里需要解释下:
myBlock明明是struct __ClangProxy__staff_block_impl_0 *
为什么可以直接转换为struct __block_impl *
?
原因很简单,因为struct __block_impl
是结构体struct __ClangProxy__staff_block_impl_0
的第一个成员变量,结构体的存储分配是按成员变量顺序来的,所以从指针开始的那段偏移量(sizeof(struct __block_impl))就是第一个成员变量,是可以强制转换的。
然后用->FuncPtr
获取到block的实现函数指针,通过第一行(void (*)(__block_impl *))
转换为带参数类型为struct __block_impl*
的函数指针;再看第三行就是传入参数myBlock;至于为什么不是跟下面函数一样传入struct __ClangProxy__staff_block_impl_0*
参数,或许是因为转化方便,毕竟他们的指针指向是一样的。
block的实现函数
static void __ClangProxy__staff_block_func_0(struct __ClangProxy__staff_block_impl_0 *__cself) {
printf("ss");
}
参数是struct __ClangProxy__staff_block_impl_0 *
,没什么特别的。
代码二:
如果上面都理解的差不多了,我们再看下面这个block(ARC,block赋值会copy到堆):
__block int i = 2;
//* printf("i在block外的地址%p\n",&i);
//+ __weak typeof(self) weakSelf = self;
// __NSMallocBlock__
void (^staBlock)(void) = ^{
//+ __strong typeof(self) strongSelf = weakSelf;
self.num = 10;
//* printf("i在block内的地址%p\n",&i);
i = 1;
//* printf("i在block内赋值后的地址%p\n",&i);
printf("112233");
};
//+ self.bbllkk = staBlock;
staBlock();
/*
i在block外的地址0x7ffeedeb0b18
i在block内的地址0x604000226ff8
i在block内赋值后的地址0x604000226ff8
*/
clang源码:
struct __Block_byref_i_2 {
void *__isa;
__Block_byref_i_2 *__forwarding;
int __flags;
int __size;
int i;
};
struct __ClangProxy__staff_block_impl_10 {
struct __block_impl impl;
struct __ClangProxy__staff_block_desc_10* Desc;
ClangProxy *self;
__Block_byref_i_2 *i; // by ref
__ClangProxy__staff_block_impl_10(void *fp, struct __ClangProxy__staff_block_desc_10 *desc, ClangProxy *_self, __Block_byref_i_2 *_i, int flags=0) : self(_self), i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ClangProxy__staff_block_func_10(struct __ClangProxy__staff_block_impl_10 *__cself) {
__Block_byref_i_2 *i = __cself->i; // bound by ref
ClangProxy *self = __cself->self; // bound by copy
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)self, sel_registerName("setNum:"), 10);
(i->__forwarding->i) = 1;
printf("112233");
}
static void __ClangProxy__staff_block_copy_10(struct __ClangProxy__staff_block_impl_10*dst, struct __ClangProxy__staff_block_impl_10*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __ClangProxy__staff_block_dispose_10(struct __ClangProxy__staff_block_impl_10*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __ClangProxy__staff_block_desc_10 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ClangProxy__staff_block_impl_10*, struct __ClangProxy__staff_block_impl_10*);
void (*dispose)(struct __ClangProxy__staff_block_impl_10*);
} __ClangProxy__staff_block_desc_10_DATA = { 0, sizeof(struct __ClangProxy__staff_block_impl_10), __ClangProxy__staff_block_copy_10, __ClangProxy__staff_block_dispose_10};
clang调用代码
__attribute__((__blocks__(byref))) __Block_byref_i_2 i = {(void*)0,(__Block_byref_i_2 *)&i, 0, sizeof(__Block_byref_i_2), 2};
void (*staBlock)(void) = ((void (*)())&__ClangProxy__staff_block_impl_10((void *)__ClangProxy__staff_block_func_10, &__ClangProxy__staff_block_desc_10_DATA, self, (__Block_byref_i_2 *)&i, 570425344));
((void (*)(__block_impl *))((__block_impl *)staBlock)->FuncPtr)((__block_impl *)staBlock);
这个block的总体结构和上面差不多,但是多了一些新的内容,
1.首先这个block内有这样两句代码
self.num = 10;
i = 1;
这里也就引用了外部变量i和self,那么block到底如何引用外部变量的呢,我们从block结构体中可以看到:
struct __ClangProxy__staff_block_impl_10 {
struct __block_impl impl;
struct __ClangProxy__staff_block_desc_10* Desc;
ClangProxy *self;
__Block_byref_i_2 *i; // by ref
__ClangProxy__staff_block_impl_10(void *fp, struct __ClangProxy__staff_block_desc_10 *desc, ClangProxy *_self, __Block_byref_i_2 *_i, int flags=0) : self(_self), i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
这里有两个成员一个self,还有一个就是 i
- 其中 self 很好理解
ClangProxy *self
,就是直接引用的类的实例,这里也是可能出现循环引用的关键点,就是block强引用了self; - 对于 i 你会发现,在block外我们创建的是
__block int i = 2
,而这里却是__Block_byref_i_2
类型,原因是我们引用了__block
,这里就会将原本的int类型转换为结构体struct __Block_byref_i_2
,
struct __Block_byref_i_2 {
void *__isa;
__Block_byref_i_2 *__forwarding;
int __flags;
int __size;
int i;
};
这个结构体第一个成员就是__isa
,类似于OC对象;关键是第二个成员__Block_byref_i_2 *__forwarding
,我们发现在block的实现函数void __ClangProxy__staff_block_func_10(...){...}
中是这么调用外部变量 i 的:(i->__forwarding->i) = 1;
,这句代码第一个i是结构体struct __Block_byref_i_2
,第二个i就是这个结构体第五个成员int i;
了,为什么要这样写呢,我们先看下面这段代码:
__block int h = 9;
printf("h在block外的地址%p\n",&h);
//__NSStackBlock__,直接在栈中被调用
^{
printf("h在block内的地址%p\n",&h);
h = 11;
printf("h在block内赋值后的地址%p\n",&h);
}();
/*
h在block外的地址0x7ffeec265c70
h在block内的地址0x7ffeec265c70
h在block内赋值后的地址0x7ffeec265c70
*/
首先我们要知道,这个block没有copy也没有赋值属于栈blockNSStackBlock
,__block int h
在clang源码中会转换为结构体struct __Block_byref_h_0
,因为一直都在栈内所以结构体struct __Block_byref_h_0
没有被拷贝,自然&h
指针,其clang代码&(h.__forwarding->h)
,__forwarding是指向自己的,最后还是指向栈中结构体h中的成员变量int h
,所以三次打印的h地址都是一样的0x7ffeec265c70
。如下图(网上找的)
我们再回头看代码二,为什么这里输出i的地址在block内外不一样呢?
/*
i在block外的地址0x7ffeedeb0b18
i在block内的地址0x604000226ff8
i在block内赋值后的地址0x604000226ff8
*/
这是因为这里block有赋值操作,原本栈中的block被copy到堆中,这个staBlock是存储在堆中的__NSMallocBlock__
。i 在block外的时候即栈中struct __Block_byref_i_2
中的__forwarding
是指向自身的,但是在block被拷贝到堆中的时候,堆中重新复制了一份变量__Block_byref_i_2 *i
,栈中i的__forwarding
就指向了堆中的i,而堆中的i的__forwarding
则指向了自己,如下(网上找的):
由于我们输出的是结构体中的
int i
这个基本类型的地址,在结构体copy到堆中的时候,即copy操作发生后,会在堆上创建新的结构体,新的结构体中的i的地址自然和copy操作前地址不一样;
2.经过对比,我们发现这里还多了这样两个函数
static void __ClangProxy__staff_block_copy_10(struct __ClangProxy__staff_block_impl_10*dst, struct __ClangProxy__staff_block_impl_10*src) {
_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __ClangProxy__staff_block_dispose_10(struct __ClangProxy__staff_block_impl_10*src) {
_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);
}
这两个函数并不是所有block都有的,当block中引用了结构体变量或者OC变量时(OC变量其实也可以理解为一种结构体),才会有这两个函数,这两个函数也只会处理block引用的结构体,其他基本类型等都不会在这两个函数里。
第一个函数__ClangProxy__staff_block_copy_10(...)
有两个__ClangProxy__staff_block_impl_10 *
结构体指针参数,分别是dst(destination,目的地),src(source,源),src就是在栈中的block结构体,而dst就是堆中的block结构体,这里有两个变量引用,所以有两个变量操作。
- 第一个是self,这是个OC对象,取栈中的self指针
src->self
,再获取堆中结构体的成员self指针的存储地址(其实就是获取成员变量在结构体中首地址)&dst->self
,3标识表示是object(8表示地址引用),正因为是object,所以只要引用指针,引用计数器+1就可以了; - 第二个是 i,它并不是一个基本类型,而是结构体指针,block外 i 是这样声明的
__block int i = 2;
,上面已有说明,这里系统会将其转换为一个结构体struct __Block_byref_i_2
;
OC对象其实也是结构体指针,但是OC对象与这里的结构体i又是不一样的:OC对象在ARC下由系统管理自动释放池,当引用计数为0,则释放。
这里的结构体指针 i 不是OC对象,所以这里在copy到堆中的时候就不是引用指针了,从&dst->i
看出,这是一个存储空间的首地址(堆中block结构体的成员 i 的存储地址),我们自然可以联想到这里应该是深拷贝,重新malloc了一个空间,然后将src->i的内容拷过去了,自然,结构体i 中 成员i 的地址就会发生变化,这里也就和上面那张引用变量在栈、堆中的关系图联系到一起了。
第二个函数__ClangProxy__staff_block_dispose_10(...)
,这里只有源栈中block结构体的引用,这是因为在堆block释放的时候,由于堆block中 结构体i 是深拷贝过来的新对象,对堆block释放的时候需要对源block中引用的变量进行主动释放
网友评论