# Block学习-关于Block是如何实现的，以及block中参数传递

int a = 0;
void (^block)(void) = ^{printf("a = %i\n",a);};
a=10;
block();

void (^block)(void) = ^(){printf("a = %i\n",a);};
int a=10;
block();

*所说的局部变量为block外的局部变量，而非block内定义的。

# Block结构：

int main(int argc, const char * argv[]) {
void (^block)(void) = ^{printf("This is a block！");};
block();
}

clang -rewrite-objc main.m
（通过clang编译器，我们可以获得在编译过程中生产的中间代码，看block时如何实现的）

struct __block_impl {
void *isa;//block存放位置，取值为_NSConcretGlobalBlock(全局区)、_NSConcretStackBlock(栈区)、_NSConcretMallocBlock(堆区)
int Flags;//用于按bit位表示一些block的附加信息，block copy的实现代码可以看到对该变量的使用。
int Reserved;//保留变量。
void *FuncPtr;
};

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)};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("This is a 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;
}
};

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);
}

__block_impl：block的一些基础属性，像是block的基类。

__main_block_desc_0：block的描述，他有一个实例__main_block_desc_0_DATA

__main_block_impl_0：block变量（包含上面两个结构体对象）。

__main_block_func_0：block的匿名函数。存放block内的语句。

struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
size_t reserved;
size_t Block_size;
__main_block_impl_0(#block匿名函数的指针#,#__main_block_desc_0的实例指针#, int flags=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) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block的匿名函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself){
int a = __cself->a; // bound by copy
printf("a = %i\n",a);
}
//主方法
int main(int argc, const char * argv[]) {
int a = 0;
//block初始化
void (*block)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);
a=10;
//block调用
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}</span>

__main_block_impl_0的属性中多了一个变量a；

__main_block_impl_0初始化方法的参数中也有接收变量对a赋值的方法。

__main_block_func_0而在匿名函数中多了一行“int a = __cself->a;”

局部变量的传递的实现方法，当block在初始化的时候，通过构造函数把a的值通过值传递传入block，而在__main_block_func_0方法中，通过int a创建变量来接收。最后在匿名函数中使用时又创建临时变量类获得a。所以在block执行时，输出的a为block初始化时获得的a的值，使用的是a的副本。

这也能说明为什么，block中不让修该局部变量。首先在block中修改局部变量的值本是没问题的，但是修改的只是和局部变量名字一样的副本变量的值，这并不会影响局部变量本来的值。为了不让开发者迷惑，所以就不让赋值。

int main(int argc, const char * argv[]) {
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:0];
void (^block)(void) = ^{
};
block();
}

# static修饰符与全局变量

全局变量或静态变量都在全局区，在内存中的地址是固定的，Block在初始化时读取的是该变量值地址，当调用的时候是直接从其所在内存读出，获取到的是最新值，而不是在定义时copy的常量。而且可以在block中修改他们的值。

int a=0;
int main(int argc, const char * argv[]) {
static int b=0;
void (^block)(void) = ^{
NSLog(@"a=%i,b=%i",++a,++b);
};
a = 10;
b = 10;
block();
}

a=11,b=11

# __block修饰符

之前了解了block中用到局部变量时，使用的是局部变量的副本，当我们要想局部变量能在block中进行修改时，我们通常会在局部变量前写上__block修饰符，那么它又是如何实现的呢。对如下代码进行编译：

int main(int argc, const char * argv[]) {
__block int a=0;
void (^block)(void) = ^{printf("a = %i",++a);};
block();
}

struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
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) {
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
printf("a = %i",++(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*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

int main(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};
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);
}
我们发现多了很多东西，看上去很乱，我们一个一个来，首先我们看主函数main里第一行，由__block int a=0;编译而来。当我们为局部变量加上__block修饰符时，编译器会自动创建一个关于这个变量的结构体__Block_byref_a_0，而变量的初始化，变为结构体的初始化，将局部变量对象化了。

在关于变量a的结构体__Block_byref_a_0中，有“int a；”用于存放变量值，有“__Block_byref_a_0 *__forwarding;”一个关于自己的指针，以及其他一些属性。而block初始化时，将对象化后的a作为了构造函数的参数。由之前分析我们已经得知如果block获取的局部变量是OC对象，是可以对它进行操作的。

为什么不直接创建变量a的指针

到这里应该理解了__block的实际工作，但是肯定会有人疑惑，说到底，将a对象化的目的只是因为对象传递得时候传的是指针，那么为什么不直接创建一个指针指向变量a，而要将他对象化。说到这里就要先说说内存中栈和堆，函数内局部变量的初始化是分配到栈中，而对象的初始化是分配在堆中（ios中alloc都是存放在堆中），变量a是分配在栈中，如果创建一栈变量的指针很危险，栈相对于堆而言是编译器自动释放，当函数结束时，栈就会被释放，而block对象通常分配在堆上，还没有被释放，而这时候原来的指针就会变成野指针，对block而言是不安全的，

 初始 copy后 arc 堆 堆 非arc 栈 堆

block对局部变量与对象的区分

在之前的编译结果中，还多出了两个方法：

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*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}

block会持有捕获的对象，包括局部变量（加了__block修饰符的）和oc对象，编译器通过BLOCK_FIELD_IS_BYREF和BLOCK_FIELD_IS_OBJECT来区分。

上面两个方法的最后一个参数都为8，表示的是BLOCK_FIELD_IS_BYREF。分别为局部变量的copy方法与释放方法。

• 本文已收录于以下专栏：

举报原因： 您举报文章：Block学习-关于Block是如何实现的，以及block中参数传递 色情 政治 抄袭 广告 招聘 骂人 其他 (最多只允许输入30个字)