群组
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截获自动变量
带有自动变量的值在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内部修改其捕获的自动变量的值,或马上报错:
告诉我们缺少__block修饰符,下面我们在看一个情况:
NSMutableArray *array = [[NSMutableArray array]init];
// MARK: - 截获自动变量
void(^BlockOne)(void) = ^void(){
[array addObject:[NSObject new]];
};
这种情况可以正常编译过去,再看一下:
同样的错误.
然后我们用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/
系列文章: