Objective-C 基础之— Block本质+源码剖析

 

block 又称之为“自带变量的匿名函数”,抛开OC语法定义block的形式不谈,其实好多语言都有类似的函数,比如JS的回调函数(其实就是将一个匿名还是作为函数的实参)、swift的闭包等等。。 
首先讲一下oc block的实质,通过自身的理解,加以各位大神的剖析文章。block 在编译时期会被编译成结构体,也就是说OC的block底层是使用C语言结构体实现的, 和对象、类的实现是一样的(所以其实block就是OC中的一个对象),这个结构体包括两个结构体成员变量和一个构造函数,我们都知道Objective-C是用OC实现底层的,所以我们需要将代码转化为底层的实现(使用Clang -rewrite-objc [文件名], 这个命令是将OC语言转化成C或者C++的代码,经常使用这个命令我们窥探OC的底层奥秘)

.m 源码
#import <Foundation/Foundation.h>
int main(int argc, char * argv[]) {
void (^myBlock)(void) = ^{
NSLog(@"hello world");
};
myBlock();
return 0;
}
 
clang 转化成的过程源码
 
// 此结构体 存储了block的isa指针,还有函数指针(调用的时候正是找到此函数指针进行调用)
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// block函数执行体转化
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dr_jkts5c395zs9xcx0bt_r7fx40000gn_T_main_7d68a8_mi_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)};
 
// 转化之后的main函数,我们可以看到block被转化成了一个函数调用,这个函数正是 __main_block_impl_0 机构体的一个构造函数,所以block被转化成了一个 __main_block_impl_0 类型的结构体; 我们看到构造函数传入了两个参数,一个函数指针,一个是结构体__main_block_desc_0实例
int main(int argc, char * argv[]) {
 
// 其实这句话就是一个指针的赋值,将一个__main_block_impl_0结构体的指针赋值给我们定义的变量myBlock,从这里可以看,OC的block会转化成一个__main_block_impl_0类型结构体
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// block 的调用, 这句代码的意思是找出__main_block_impl_0结构体的imp结构体(即是一个__block_impl的结构体)然后调用其函数指针FuncPtr,从而调用block内的函数执行体(当然这是过程代码,并没有直接标出myBlock.imp.FuncPtr)
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
 
return 0;
}
// block的结构体 ,包含两个结构体和一个构造函数,
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;
}
};

上面的源码转换成C代码之后,我做了一次完全的剖析,我们可以得出的结论就是,block 的实现其实就是一个结构体,而其的调用则是调用结构体内的函数指针调用函数;下面我将稍微改动一下代码,看一下生成的底层代码,研究一下,截获的外部变量的情况:

我只将不同的代码提出来
.m源代码
int main(int argc, char * argv[]) {
int a = 10;
void (^myBlock)(void) = ^{
NSLog(@"hello world %d", a);
};
myBlock();
return 0;
}
clang 转换C代码
// 我们看出来了这个时候__main_block_func_0的结构体的构造函数,多传了一个变量a的参数,我们看到,并没有把a的变量指针传递,而是将变量的值传递
int main(int argc, char * argv[]) {
int a = 10;
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
 
return 0;
}
 
// block 函数执行体当然也会变了,生成了一个变量,这个变量是__main_block_impl_0结构体里的变量a
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
 
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dr_jkts5c395zs9xcx0bt_r7fx40000gn_T_main_3e8c5a_mi_0, a);
}
// 重要的结构体来了,这个结构体内生成了一个成员变量a
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截获的变量只是一个值,而并不是指针,所以,内部执行函数无法改变block外部的变量 
通过上边的例子,我们可以猜想,如果block的结构体如果可以拥有变量的指针那么,就可以修改外部变量的值了 ,所以 _ block就是做这件事情的,其实_ block的实现远比我们想象的复杂,我们看一下__block 修饰之后转换的代码:

// 我们可以看到加了__block 的变量和不加的变量发生明显的变化,加了__block的变量明显被转化成了一个结构体__Block_byref_c_0, 而且__main_block_impl_0block的结构体正是吧__Block_byref_c_0结构体类型的指针传入了
int main(int argc, char * argv[]) {
int a = 10;
int b = 20;
__attribute__((__blocks__(byref))) __Block_byref_c_0 c = {(void*)0,(__Block_byref_c_0 *)&c, 0, sizeof(__Block_byref_c_0), 30};
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b, (__Block_byref_c_0 *)&c, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
 
return 0;
}
// 加了__block 的变量被转化的结构提类型
struct __Block_byref_c_0 {
void *__isa;
__Block_byref_c_0 *__forwarding;
int __flags;
int __size;
int c;
};
 
// block的结构体也形成了一个__Block_byref_c_0结构体指针
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
int b;
__Block_byref_c_0 *c; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, __Block_byref_c_0 *_c, int flags=0) : a(_a), b(_b), c(_c->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block的执行函数 也相应的生成了一个C结构体的指针指向block结构的C
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_c_0 *c = __cself->c; // bound by ref
int a = __cself->a; // bound by copy
int b = __cself->b; // bound by copy
 
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dr_jkts5c395zs9xcx0bt_r7fx40000gn_T_main_55b164_mi_0, a, b, (c->__forwarding->c));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->c, (void*)src->c, 8/*BLOCK_FIELD_IS_BYREF*/);}
 
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->c, 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),

所以我们可以看得出来_ block 的作用是生成外部变量的指针,从而达到在block内部可以修改的目的;下一篇文章将会结合这篇文章Block的本质,总结_ weak _ block _ strong的使用。 
致谢:https://blog.csdn.net/abc649395594/article/details/47086751 
https://www.jianshu.com/p/fdd7fa9a9e7e 
两篇文章的对我的帮助!

 
 
 

原文地址:https://www.cnblogs.com/wannaGoBoy/p/9052697.html