Objective-C高级编程+iOS与OS+X多线程和内存管理 读书笔记之 block

block概要

概念:block是带有自动变量匿名函数

block模式

1. 语法:

^ 返回值类型(可省略)`参数列表表达式`

1
2
3
^ int (int count) {
return count + 1
}

2. block类型变量

当函数参数与返回值使用block类型时,记述方式比较复杂,可以用 typedef 来解决

typedef int (^block_t)(int);
void func(block_t block)
block_t func()

3.自动截获变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...

int a = 0;
void(^block)(void) = ^(){
NSLog(@"%d", a);
};
a = 1;
block();

}
return 0;
}

打印的出来的是 0

block表达式截获所使用的自动变量的值,即保存自动变量的瞬时值

4. __block修饰符

block只能截获自动变的瞬时值,但不能给自动变量赋值
使用__block修饰符的自动变量,可以在block表达式中被赋值 自动变量截获没有实现对C语言数组的截获, 使用时编译器会报错

block实现

编译前源码

1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
int a = 0;
void(^block)(void) = ^(){
printf("%d", a);
};
a = 1;
block();
}
return 0;
}

编译后源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14

int main(int argc, const char * argv[]) {

/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;


int a = 0;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
a = 1;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

}
return 0;
}

先来看看 __main_block_func_0 的定义

1
2
3
4
5
6
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 我们的源码的编译后的函数体,值引用了变量 a
int a = __cself->a; // bound by copy

printf("%d", a);
}

main_block_impl_0 又是什么呢 ,看一下声明 main_block_impl_0是一个结构体,有三个成员

  1. struct block_impl impl // 结构体 block_impl 应该是block的本质
  2. struct __main_block_desc_0 Desc
  3. int a
1
2
3
4
5
6


struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;

再看看结构体 __block_impl 的定义,四个成员

  1. isa指针
  2. Flags
  3. Reserved
  4. 函数指针FuncPtr,函数指针指向源码编译后生成的函数,也就是__main_block_func_0
1
2
3
4
5
6
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

再回头看看__main_block_impl_0 的构造函数:

主要是 给block_impl赋值,以及截获自动变量。可以看到block_impl的isa指针被赋值为_NSConcreteStackBlock

1
2
3
4
5
6
7
  __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;
}
};

结构体main_block_impl_0还有一个成员没有分析到: struct main_block_desc_0* Desc;

1
2
3
4
5

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_impl_0 的实例大小

至此,所有相关的结构体都分析了,回过头来看看,编译后的源码
再贴一下,翻回去再翻回来挺累的,去掉了转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(int argc, const char * argv[]) {

/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;


int a = 0;
// 构造block
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
a = 1;

// 调用block, FuncPtr也就是__main_block_func_0 入参就是blcok
block->FuncPtr(block)

}
return 0;
}

这就很清楚了,其实就是编译器生成了FuncPtr函数和block结构体,结构体值引用了自动变量(所以说是截获自动变量),调用的时候执行下FuncPtr。还有一个问题 _NSConcreteStackBlock 是个什么类?

__block 说明符

block中给变量赋值编译会报错。两种情况下允许在block中进行赋值操作。

  1. 全局变量、静态全局变量和静态变量
  2. __block 说明符修饰的变量

全局变量与全局静态变量,在block中是直接使用
静态变量则是是用静态变量的指针进行访问

__block 说明符编译后

编译前

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, const char * argv[]) {

@autoreleasepool {
// insert code here...
__block int a = 3;
void(^block)(void) = ^(){
a *=2;
};
block();

}
return 0;
}

编译后

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, const char * argv[]) {

/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// __block int a = 3 变成了 __Block_byref_a_0 结构体 并初始化为3
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 3};
// 初始化block
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
// 执行block
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

}
return 0;
}

主要变化在main_block_impl_0 结构体,变量a 不在是一个int 而是Block_byref_a_0 结构体

1
2
3
4
5
6
7
8
9
10
11
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;
}
};

来看看__Block_byref_a_0

__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 3};

1
2
3
4
5
6
7
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};

可以看到结构体中的__forwarding 就是指向了自身 int a 被初始化为3
放一张图来说明

block的存储域

问题:

  1. block超出变量作用域可以存在的原因

答案:block从栈上复制到了对堆

  1. forwarding的作用是什么
    答案:
    forwarding保证不管block被分配的堆上还是在栈上,都能被访问到

__block变量的存储域

截获对象

__block变量与对象