下面我将经过一个简单的例子,结合源代码进行介绍api
int main(int argc, const char * argv[]) {
void (^blk)(void) = ^{ printf("Hello Block\n"); };
blk();
return 0;
}
复制代码
使用clang -rewrite-objc main.m
,咱们能够将 Objc 的源码转成 Cpp 的相关源码:bash
int main(int argc, const char * argv[]) {
// Block 的建立
void (*blk)(void) =
(void (*)(void))&__main_block_impl_0(
(void *)__main_block_func_0, &__main_block_desc_0_DATA);
// Block 的使用
((void (*)(struct __block_impl *))(
(struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
return 0;
}
复制代码
由上面的源码,咱们能猜测到:数据结构
__main_block_impl_0
结构体FuncPtr
函数指针的调用从这里为切入点看看上面提到的都是啥函数
Block 的真身:ui
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 省略了构造函数
};
复制代码
__main_block_impl_0
名字的命名规则: __所在函数_block_impl_序号
__main_block_impl_0
的主要数据:this
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
复制代码
isa
指针: 体现了 Block 是 Objc 对象的本质。FuncPtr
指针: 其实就是一个函数指针,指向所谓的匿名函数。__main_block_desc_0
中放着 Block 的描述信息spa
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
即 Block 建立时候使用到了__main_block_func_0
正是下面的函数:3d
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Hello Block\n");
}
复制代码
^{ printf("Hello Block\n"); }
十分类似,由此可看出: 经过 Blocks 使用的匿名函数实际上被做为简单的 C 语言函数来处理main
)和该 Block 语法在函数出现的顺序值(此处为 0)来命名的。__cself
至关于 C++ 实例方法中指向实例自身的变量this
,或是 Objective-C 实例方法中指向对象自身的变量self
,即参数__cself
为指向 Block 的变量。(*blk->impl.FuncPtr)(blk);
中的blk
就是__cself
介绍了基本的数据结构,下面到回到一开始的main
函数,看看 Block 具体的使用指针
void (*blk)(void) =
(void (*)(void))&__main_block_impl_0(
(void *)__main_block_func_0, &__main_block_desc_0_DATA);
/** 去掉转换的部分
struct __main_block_impl_0 tmp =
__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
*/
复制代码
void (^blk)(void)
就是是一个struct __main_block_impl_0 *blk
__main_block_func_0
的函数指针建立一个__main_block_impl_0
结构体,咱们用的时候是拿到了这个结构体的指针。((void (*)(struct __block_impl *))(
(struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
/** 去掉转换的部分
(*blk->impl.FuncPtr)(blk);
*/
复制代码
__main_block_impl_0
中的函数指针FuncPtr
(blk)
这里是传入本身,就是给_cself
传参从 Block 中的简单实现中,咱们从
isa
中发现 Block 的本质是 Objc 对象,是对象就有不一样类型的类。所以,Block 固然有不一样的类型code
在 Apple 的libclosure-73
中的data.c
上可见,isa
可指向:
void * _NSConcreteStackBlock[32] = { 0 }; // 栈上建立的block
void * _NSConcreteMallocBlock[32] = { 0 }; // 堆上建立的block
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteGlobalBlock[32] = { 0 }; // 做为全局变量的block
void * _NSConcreteWeakBlockVariable[32] = { 0 };
复制代码
其中咱们最多见的是:
Block的类型 | 名称 | 行为 | 存储位置 |
---|---|---|---|
_NSConcreteStackBlock | 栈Block | 捕获了局部变量 | 栈 |
_NSConcreteMallocBlock | 堆Block | 对栈Block调用copy所得 | 堆 |
_NSConcreteGlobalBlock | 全局Block | 定义在全局变量中 | 常量区(数据段) |
PS: 内存五大区:栈、堆、静态区(BSS 段)、常量区(数据段)、代码段
对象有copy
操做,Block 也有copy
操做。不一样类型的 Block 调用copy
操做,也会产生不一样的复制效果:
Block的类型 | 副本源的配置存储域 | 复制效果 |
---|---|---|
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock | 常量区(数据段) | 什么也不作 |
_NSConcreteMallocBlock | 堆 | 引用计数增长 |
copy
实例方法编译器自动调用_Block_copy
函数状况
id
或 Block 类型成员变量)PS: 在 ARC 环境下,声明的 Block 属性用copy
或strong
修饰的效果是同样的,但在 MRC 环境下用 copy 修饰。
以全局变量、静态全局变量、局部变量、静态局部变量为例:
int global_val = 1;
static int static_global_val = 2;
int main(int argc, const char * argv[]) {
int val = 3;
static int static_val = 4;
void (^blk)(void) = ^{
printf("global_val is %d\n", global_val);
printf("static_global_val is %d\n", static_global_val);
printf("val is %d\n", val);
printf("static_val is %d\n", static_val);
};
blk();
return 0;
}
复制代码
转换后“匿名函数”对应的代码:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
int *static_val = __cself->static_val; // bound by copy
printf("global_val is %d\n", global_val);
printf("static_global_val is %d\n", static_global_val);
printf("val is %d\n", val);
printf("static_val is %d\n", (*static_val));
}
复制代码
__main_block_impl_0
中存在val
实例,所以对于局部变量,Block 只是单纯的复制建立时候局部变量的瞬时值,咱们可使用值,但不能修改值。struct __main_block_impl_0 {
// ...
int val; // 值传递
// ...
};
复制代码
__main_block_impl_0
中存在static_val
指针,所以 Block 是在建立的时候获取静态局部变量的指针值。struct __main_block_impl_0 {
// ...
int *static_val; // 指针传递
// ...
};
复制代码
模仿基础类型变量,实例化四个不同的SCPeople
变量:
int main(int argc, const char * argv[]) {
// 省略初始化
[globalPeople introduce];
[staticGlobalPeople introduce];
[people introduce];
[staticPeople introduce];
return 0;
}
复制代码
转换后"匿名函数"对应的代码:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
SCPeople *people = __cself->people; // bound by copy
SCPeople **staticPeople = __cself->staticPeople; // bound by copy
// 省略 objc_msgSend 转换
[globalPeople introduce];
[staticGlobalPeople introduce];
[people introduce];
[*staticPeople introduce];
}
复制代码
__main_block_impl_0
中存在people
指针实例,所以 Block 获取的是指针瞬间值,咱们能够在 Block 中经过指针能够操做对象,可是不能改变指针的值。struct __main_block_impl_0 {
// ...
SCPeople *people;
// ...
};
复制代码
__main_block_impl_0
中存在staticPeople
指针的指针,所以 Block 是在建立的时候获取静态局部对象的指针值(即指针的指针)。struct __main_block_impl_0 {
// ...
SCPeople **staticPeople;
// ...
};
复制代码
经过对基础类型、对象类型与四种不一样的变量进行排列组合的小 Demo,不可贵出下面的规则:
变量类型 | 是否捕获到 Block 内部 | 访问方式 |
---|---|---|
全局变量 | 否 | 直接访问 |
静态全局变量 | 否 | 直接访问 |
局部变量 | 是 | 值访问 |
静态局部变量 | 是 | 指针访问 |
PS:
上面的篇幅经过底层实现,向你们介绍了 Block 这个所谓"匿名函数"是如何捕获变量的,可是一些时候咱们须要修改 Block 中捕获的变量:
全局变量与静态全局变量的做用域都是全局的,天然在 Block 内外的变量操做都是同样的。
在上面变量捕获的章节中,咱们得知 Block 捕获的是静态局部变量的指针值,所以咱们能够在 Block 内部改变静态局部变量的值(底层是经过指针来进行操做的)。
使用
__block
修饰符来指定咱们想改变的局部变量,达到在 Block 中修改的须要。
咱们用一样的方式,经过底层实现认识一下__block
,举一个🌰:
__block int val = 0;
void (^blk)(void) = ^{ val = 1; };
blk();
复制代码
通过转换的代码中出现了和单纯捕获局部变量不一样的代码:
__Block_byref_val_0
结构体struct __Block_byref_val_0 {
void *__isa; // 一个 Objc 对象的体现
__Block_byref_val_0 *__forwarding; // 指向该实例自身的指针
int __flags;
int __size;
int val; // 原局部变量
};
复制代码
__block
修饰的变量包装成一个 Objc 对象。val
转换成__Block_byref_val_0
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
0
};
复制代码
__main_block_impl_0
捕获的变量struct __main_block_impl_0 {
// ...
__Block_byref_val_0 *val; // by ref
// ...
};
复制代码
__main_block_impl_0
结构体实例持有指向__block
变量的__Block_byref_val_0
结构体实例的指针。static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
复制代码
val
是__main_block_impl_0
中的val
,这个val
经过__block int val
的地址初始化val
是__Block_byref_val_0
中的val
,正是__block int val
的val
__forwarding
在这里只是单纯指向了本身而已
上面的"栈Blcok"中__forwarding
在这里只是单纯指向本身,可是在当"栈Blcok"复制变成"堆Block"后,__forwarding
就有他的存在乎义了:
__block
修饰符不能用于修饰全局变量、静态变量。
众所周知,对象其实也是使用一个指针指向对象的存储空间,咱们的对象值其实也是指针值。虽然是看似对象类型的捕获与基础类型的指针类型捕获差很少,可是捕获对象的转换代码比基础指针类型的转换代码要多。(__block
变量也会变成一个对象,所以下面的内容也适用于__block
修饰局部变量的状况)。多出来的部分是与内存管理相关的copy
函数与dispose
函数:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->people, (void*)src->people, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_assign((void*)&dst->staticPeople, (void*)src->staticPeople, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->people, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_dispose((void*)src->staticPeople, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
复制代码
这两个函数在 Block 数据结构存在于Desc
变量中:
static struct __main_block_desc_0 {
// ...
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
}; // 省略了初始化好的结构体
复制代码
函数 | 调用时机 |
---|---|
copy 函数 | 栈上的 Block 复制到堆时 |
dispose 函数 | 堆上的 Block 被废弃时 |
copy
函数中的_Block_object_assign
函数至关于内存管理中的retain
函数,将对象赋值在对象类型的结构体成员变量中。dispose
函数中的_Block_object_dispose
函数至关于内存管理中的release
函数,释放赋值在对象类型的结构体变量中的对象。copy
和dispose
并配合 Objc 运行时库对其的调用能够实现内存管理当 Block 内部访问了对象类型的局部变量时:
copy
到堆上时: Block 会调用内部的copy
函数,copy
函数内部会调用_Block_object_assign
函数,_Block_object_assign
函数会根据局部变量的修饰符(__strong
、__weak
、__unsafe_unretained
)做出相应的内存管理操做。(注意: 多个 Block 对同一个对象进行强引用的时,堆上只会存在一个该对象)dispose
函数,dispose
函数内部会调用_Block_object_dispose
函数,_Block_object_dispose
函数会自动release
引用的局部变量。(注意: 直到被引用的对象的引用计数为 0,这个堆上的该对象才会真正释放)PS: 对于__block
变量,Block 永远都是对__Block_byref_局部变量名_0
进行强引用。若是__block
修饰符背后还有其余修饰符,那么这些修饰符是用于修饰__Block_byref_局部变量名_0
中的局部变量
的。
现象: Block 中使用的赋值给附有
__strong
修饰符的局部变量的对象和复制到堆上的__block
变量因为被堆的 Block 所持有,于是可超出其变量做用域而存在。
因为 Block 内部能强引用捕获的对象,所以当该 Block 被对象强引用的时候就是注意如下的引用循环问题了:
弱引用持有:使用__weak
或__unsafe_unretained
捕获对象解决
weak
修饰的指针变量,在指向的内存地址销毁后,会在 Runtime 的机制下,自动置为nil
。_unsafe_unretained
不会置为nil
,容易出现悬垂指针,发生崩溃。可是_unsafe_unretained
比__weak
效率高。使用__block
变量:使用__block
修饰对象,在 block 内部用完该对象后,将__block
变量置为nil
便可。虽然能控制对象的持有期间,而且能将其余对象赋值在__block
变量中,可是必须执行该 block。(意味着这个对象的生命周期彻底归咱们控制)
__unsafe_unretained
捕获对象__block
修饰对象,无需手动将对象置为nil
,由于底层_Block_object_assign
函数在 MRC 环境下对 block 内部的对象不会进行retain
操做。ARC 无效时,须要手动将 Block 从栈复制到堆,也须要手动释放 Block
retain
实例方法是不起做用的copy
实例方式(引用计数+1),将其配置在堆上,才可继续使用retain
实例方法release
实例方法便可。Block_copy
和Block_release
代替copy
和release
。