Block解读

群组
Flutter,iOS,Android,Python等,闲聊工作&技术&问题;
个人主页:https://waitwalker.cn
telegram群链接:https://t.me/joinchat/Ej0o0A1ntlq5ZFIMzzO5Pw

Block定义

        看过<<Objective-C高级编程iOS与OS X多线程和内存管理>>这本书的朋友应该知道其对block的定义:Blocks是C语言的扩充功能,带有自动变量(局部变量)的匿名函数.匿名函数就是不带有名称的函数.Block在不同的语言中有不同的命名.Python中叫Lambda,Swift中叫Closure(闭包)等.

我们知道根据作用域的不同变量大概有:自动变量(局部变量),静态变量(静态局部变量),静态全局变量,全局变量.而静态变量(局部和全局),全局变量其生命周期一般与程序同步,在程序的整个生命周期内,其也会保持在同一内存区域.

block的声明结构


// MARK: - blcok声明结构

struct Block_layout {

void *isa;

volatile int32_t flags; // contains ref count

int32_t reserved;

void (*invoke)(void *, ...);

struct Block_descriptor_1 *descriptor;

// imported variables

};

// MARK: - block 附件信息1

#define BLOCK_DESCRIPTOR_1 1

struct Block_descriptor_1 {

uintptr_t reserved;

uintptr_t size;

};

// MARK: - block 附件信息2

#define BLOCK_DESCRIPTOR_2 1

struct Block_descriptor_2 {

// requires BLOCK_HAS_COPY_DISPOSE

void (*copy)(void *dst, const void *src);

void (*dispose)(const void *);

};

// MARK: - block 附件信息3

#define BLOCK_DESCRIPTOR_3 1

struct Block_descriptor_3 {

// requires BLOCK_HAS_SIGNATURE

const char *signature;

const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT

};

struct Block_byref {

void *isa;

struct Block_byref *forwarding;

volatile int32_t flags; // contains ref count

uint32_t size;

};

struct Block_byref_2 {

// requires BLOCK_BYREF_HAS_COPY_DISPOSE

void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);

void (*byref_destroy)(struct Block_byref *);

};

struct Block_byref_3 {

// requires BLOCK_BYREF_LAYOUT_EXTENDED

const char *layout;

};

只在block_private.h中只找到block相关的声明结构,具体实现没有找到.其实现源码这里).下面我们看一下block_layout声明结构中的一些成员变量:

isa:block类型指针

flags:标志位,用于按bit操作

reserved:保留变量

invoke:指向block实现函数的指针

descriptor:附件信息

这些具体的结构我们可以通过clang编译器重写Objective-C文件转成.cpp文件查看源码.

Block语法

返回值类型 (^block名称)(形参参数列表) = ^返回值类型(参数列表){

return 返回值

}

Block语法.jpg

Block截获自动变量

带有自动变量的值在block中表现为截获自动变量值.


int valueOne = 18;

int valueTwo = 1;

// MARK: - 截获自动变量

void(^BlockOne)(void) = ^void(){

NSLog(@"valueOne:%d; valueTwo:%d",valueOne,valueTwo);

};

// 修改自动变量的值

valueOne = 9;

// 调用block

BlockOne();

调用block后我们看到log输出:


2019-04-17 15:30:28.442314+0800 Block_Layout[6530:443419] valueOne:18; valueTwo:1

        block内部捕获了valueOne和valueTwo两个自动变量,其中valueOne在block调用前进行了修改,而在block在真实调用时,valueOne值并没有跟随修改,因为block表达式截获得是自动变量的瞬间值,保存后就不能改写.所以在调用block时,即使改了block截获的自动变量的值,也不会影响block表达式内截获的值.

通过clang编译器重写一些文件,获取底层实现源码:


// block 声明结构

struct __block_impl {

void *isa;

int Flags;

int Reserved;

void *FuncPtr;

};

// block声明&实现结构

void(*BlockOne)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, valueOne, valueTwo));

valueOne = 9;

// block调用

((void (*)(__block_impl *))((__block_impl *)BlockOne)->FuncPtr)((__block_impl *)BlockOne);

// MARK: - block具体实现结构

struct __ViewController__viewDidLoad_block_impl_0 {

struct __block_impl impl;

struct __ViewController__viewDidLoad_block_desc_0* Desc;

int valueOne;

int valueTwo;

// 同名的构造函数

__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _valueOne, int _valueTwo, int flags=0) : valueOne(_valueOne), valueTwo(_valueTwo) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

// MARK: - block实现结构对应的实现函数

static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {

int valueOne = __cself->valueOne; // bound by copy

int valueTwo = __cself->valueTwo; // bound by copy

NSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_0qh5fq653wq1l3qz9_3ftg3r0000gn_T_ViewController_75dfcb_mi_0,valueOne,valueTwo);

}

// MARK: - desc结构

static struct __ViewController__viewDidLoad_block_desc_0 {

size_t reserved;

size_t Block_size;

} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};

具体看一下这几个结构:

__block_impl:block对外暴露的声明结构,内部有四个成员变量.

_block_impl_0:block内部实现结构,其内部有一个同名的构造函数.

_block_func_0:block实现结构对应的实现函数,我们对block捕获的一些值都在这里操作,其参数为__cself指向的是Block自身.

_block_desc_0:desc结构,描述_block_impl_0的size等.

ViewControllerviewDidLoad_block_impl_0()构造函数,将fp函数指针赋值给impl的FuncPtr,将desc赋值给 Desc ,将flags赋值给impl的flags,将Block类型赋值给isa,之前我们已经讲过isa),可以具体查看,到此我们可以确定block的本质其实就是Objective-C对象,这些赋值后完成对impl进行初始化.

blockOne就是一个_block_impl_0的实例.

        可以以方法类比:__block_impl是对外的接口,其内部实现是_block_impl_0来实现的.我们可以看到block实现表达式中捕获的直接是valueOne,valueTwo的值.

如果我们尝试在block内部修改其捕获的自动变量的值,或马上报错:

missing__block_error.png

告诉我们缺少__block修饰符,下面我们在看一个情况:


NSMutableArray *array = [[NSMutableArray array]init];

// MARK: - 截获自动变量

void(^BlockOne)(void) = ^void(){

[array addObject:[NSObject new]];

};

这种情况可以正常编译过去,再看一下:

missing__block.png

同样的错误.

        然后我们用block修饰符修饰一下自动变量,编译正常通过.因此,如果我们需要修改block捕获的自动变量,需要在自动变量前面添加block修饰符.

Block捕获静态/全局变量


// 静态全局变量

static int valueTwo = 20;

// 全局变量

int valueThree = 30;

@interface ViewController ()

@property (nonatomic, copy) NSString *name;

@property (nonatomic, copy) NSString *valueString;

@end

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

// 静态局部变量

static int valueOne = 10;

// MARK: - 截获静态变量,全局变量

void(^BlockOne)(void) = ^void(){

valueOne = 1;

valueTwo = 2;

valueThree = 3;

NSLog(@"valueOne:%d,valueTwo:%d,valueThree:%d",valueOne,valueTwo,valueThree);

};

// 修改自动变量的值

valueOne = 9;

// 调用block

BlockOne();

}

@end

日志输出:


2019-04-17 16:56:57.112848+0800 MTTRuntime[7888:540053] valueOne:1,valueTwo:2,valueThree:3

然后我们继续用clang编译转换一下:


// block 声明和实现结构

void(*BlockOne)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, &valueOne));

valueOne = 9;

// block调用

((void (*)(__block_impl *))((__block_impl *)BlockOne)->FuncPtr)((__block_impl *)BlockOne);

struct __ViewController__viewDidLoad_block_impl_0 {

struct __block_impl impl;

struct __ViewController__viewDidLoad_block_desc_0* Desc;

int *valueOne;

__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int *_valueOne, int flags=0) : valueOne(_valueOne) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {

int *valueOne = __cself->valueOne; // bound by copy

// 静态局部变量

(*valueOne) = 1;

// 静态全局变量

valueTwo = 2;

// 全局变量

valueThree = 3;

NSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_0qh5fq653wq1l3qz9_3ftg3r0000gn_T_ViewController_9483ca_mi_0,(*valueOne),valueTwo,valueThree);

}

static struct __ViewController__viewDidLoad_block_desc_0 {

size_t reserved;

size_t Block_size;

} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};

        我们可以看到静态全局变量和全局变量,被block捕获后直接修改的是其值,因为全局变量作用域于全局,在block内部进行修改后其值可以被保存下来.

        而静态局部变量访问的是使用指针对其进行访问,在block实现表达式里面传进去的也是&valueOne地址.

block在捕获全局变量/静态变量时,可以在block实现表达式内部对其进行更改.block捕获静态局部变量的地址并保存,通过操作指针实现值的修改.

Block捕获__block修饰的自动变量


- (void)viewDidLoad {

[super viewDidLoad];

// __block修饰的自动变量

__block int valueOne = 10;

// MARK: - 截获__block修饰的自动变量

void(^BlockOne)(void) = ^void(){

valueOne = 1;

NSLog(@"valueOne:%d",valueOne);

};

// 修改自动变量的值

valueOne = 9;

// 调用block

BlockOne();

}

控制台输出:


2019-04-18 08:54:48.274042+0800 MTTRuntime[4012:43868] valueOne:1,valueTwo:20,valueThree:30

被__block修饰的自动变量,block捕获后能够对其进行更改.clang重写一下,查看源码:


// __block修饰的成员变量转换

__attribute__((__blocks__(byref))) __Block_byref_valueOne_0 valueOne = {(void*)0,(__Block_byref_valueOne_0 *)&valueOne, 0, sizeof(__Block_byref_valueOne_0), 10};

// block的声明和实现表达式

void(*BlockOne)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, (__Block_byref_valueOne_0 *)&valueOne, 570425344));

// 修改valueOne的值

(valueOne.__forwarding->valueOne) = 9;

//block调用

((void (*)(__block_impl *))((__block_impl *)BlockOne)->FuncPtr)((__block_impl *)BlockOne);

// byref结构

struct __Block_byref_valueOne_0 {

void *__isa;

__Block_byref_valueOne_0 *__forwarding;//指向自己的指针

int __flags;

int __size;

int valueOne;

};

//block匿名实现结构

struct __ViewController__viewDidLoad_block_impl_0 {

struct __block_impl impl;

struct __ViewController__viewDidLoad_block_desc_0* Desc;

__Block_byref_valueOne_0 *valueOne; // by ref

__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_valueOne_0 *_valueOne, int flags=0) : valueOne(_valueOne->__forwarding) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

// block实现函数

static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {

__Block_byref_valueOne_0 *valueOne = __cself->valueOne; // bound by ref

(valueOne->__forwarding->valueOne) = 1;

NSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_0qh5fq653wq1l3qz9_3ftg3r0000gn_T_ViewController_ea5a30_mi_0,(valueOne->__forwarding->valueOne));

}

// block copy

static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->valueOne, (void*)src->valueOne, 8/*BLOCK_FIELD_IS_BYREF*/);}

// block dispose

static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->valueOne, 8/*BLOCK_FIELD_IS_BYREF*/);}

        可以看到,这里多了Block_byref_valueOne_0结构,我们初始化的自动变量也被转换成了这个结构的实例,自动变量初始化值为10,这个值也出现在Block_byref_valueOne_0结构的初始化中,并且这个结构持有这个变量,使其成为成员变量.


// byref结构

struct __Block_byref_valueOne_0 {

void *__isa;

__Block_byref_valueOne_0 *__forwarding;//指向自己的指针

int __flags;

int __size;

int valueOne;

};

我们再来看一下怎么访问这个自动变量的:


// block实现函数

static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {

__Block_byref_valueOne_0 *valueOne = __cself->valueOne; // bound by ref

(valueOne->__forwarding->valueOne) = 1;

NSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_0qh5fq653wq1l3qz9_3ftg3r0000gn_T_ViewController_ea5a30_mi_0,(valueOne->__forwarding->valueOne));

}

        是通过((Block_byref_valueOne_0 *)valueOne->forwarding->valueOne).Block的_block_impl_0结构实例指针持有指向Block_byref_valueOne_0的指针;而Block_byref_valueOne_0的成员变量持有指向自身的指针.然后通过forwarding来访问valueOne.Block_byref_valueOne_0结构并不在_block_impl_0中,这样做主要是在多个Block中使用__block.

Block类型

_NSConcreteGlobalBlock:定义在全局区,存储于程序数据区

_NSConcreteStackBlock:存储于栈区,超出作用域被回收

_NSConcreteMallocBlock:存储于堆区,从栈copy过来

Block复制到堆上的情况:

1)block调用了copy方法;

2)block作为方法返回值;

3)将block赋值给__strong修饰的id类型或赋值给类的block成员变量;

4)方法名中有usingBlock的cocoa框架或者GCD相关api;

Block循环引用

        如果Block中使用附有strong修饰符的对象类型自动变量,那么Block从栈复制到堆上时,该对象为Block所持有,这样会引起循环引用问题,可以通过weak修饰符来打破循环引用,这个我们在后面细讲__weak实现原理.

编译后源码库

编译后的源码放在Github, 如果对你有帮助,请给一个star吧!

博客地址&相关文章

博客地址: https://waitwalker.github.io/

系列文章:

1. Runtime源码编译

2. objc_object解读

3. Method解读

4. Class解读

5. Ivar objc_property_t Protocol解读

6. Block解读

7. Retain&Release解读

8. Autorelease解读

9. Weak解读

10. msgSend()解读


  转载请注明: waitwalker Block解读

  目录