1. 什么是Block?
Block是将函数及其执行上下文封装起来的对象。
// Block源码
struct __block_impl {
void *isa;
int Flags;
int Reserved;
// 保存内部代码块执行的函数地址
void *FuncPtr;
};
// Block底层的结构
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 构造函数(类似于OC的init方法),返回结构体对象
__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;
}
};
// 封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_3df4b0_mi_0);
}
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)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 定义block变量
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
// 执行block内部的代码
block->FuncPtr(block);
}
return 0;
}
复制代码
- Block底层数据结构是
__main_block_impl_0
类型,定义的block变量是指向__main_block_impl_0
类型的对象类型指针。 __main_block_impl_0
构造方法的需要两个参数:第一个参数:__main_block_func_0
函数是对Block内部代码块的封装;第二个参数类型是__main_block_desc_0_DATA
类型指针,内部主要对Block的信息封装,比如包含block的占用内部空间大小。- 在
__main_block_impl_0
构造方法中将封装block代码块的__main_block_func_0
函数地址传入__main_block_impl_0
对象的FuncPtr成员保存。 - 当执行block,直接通过指针访问到FuncPtr成员的函数地址进行调用。
什么是Block调用?
Block调用即是函数的调用。
2. 截获变量
- 对于基本数据类型的局部变量截获其值。
- 对于对象类型的局部变量连同所有权修饰符一起截获。
- 以指针形式截获局部静态变量。
- 不截获全局变量、静态全局变量。
3. __block修饰符
- 一般情况下,对被截获变量进行赋值操作需添加__block修饰符。
- 赋值和使用是两个场景(例如只是为可变数组类型的局部变量添加元素,就只是使用,并不需要__block修饰)。
由于截获变量的特征,对基本数据类型和对象类型的局部变量赋值时,需使用__block修饰;而对静态局部变量、全局变量和静态全局变量赋值时,并不需要使用__block修饰符。虽然可以通过定义static或者全局变量来实现在block内部修改变量,但是全局变量和static会修改变量的作用域,因此开发中一般使用__block。
1) __block是如何实现的?
__block之所以可以修改局部变量,是因为编译器将__block修饰的变量包装成了一个对象。
对象中包含:
- __isa
- __forwarding
- __flags
- __size
- val // 值
2) __forwarding指向
- 若block在栈上,且未做过copy操作,则__block对象其中的__forwarding指针指向自己。
- 栈上的block复制到堆上后,则栈上的__block对象和堆上copy出的对象,其中的__forwarding指针都指向堆中的__block对象。
__forwarding的存在是为了不论在任何内存位置,都可以顺利的访问同一个__block变量。
4. Block内存管理
1) Block有哪几种类型?
block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型。
_NSConcreteStackBlock
:存储在栈区_NSConcreteMallocBlock
:存储在堆区_NSConcreteGlobalBlock
:存储在全局区的.data段
没有访问auto变量的block存储于数据段;访问了auto变量的block存储于栈区;对栈区的block做copy操作,其结果存储于堆区。
Block的Copy操作
Block类别 | 源 | Copy结果 |
---|---|---|
_NSConcreteStackBlock |
栈 | 堆 |
_NSConcreteMallocBlock |
堆 | 增加引用计数 |
_NSConcreteGlobalBlock |
数据区 | 什么也不做 |
2) 当block内部访问了对象类型的auto变量时,是否会强引用?
- 如果block是在栈上,将不会对auto变量产生强引用。
- 当block被拷贝到堆时:
- 会调用block内部的copy函数;
- copy函数内部会调用
_Block_object_assign
函数; _Block_object_assign
函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。
- 当block从堆中移除时:
- 会调用block内部的dispose函数;
- dispose函数内部会调用
_Block_object_dispose
函数; _Block_object_dispose
函数会自动释放引用的auto变量(release)。
- 如果block在栈空间,不管外部变量是强引用还是弱引用,block都会弱引用访问对象;
- 如果block在堆空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用。
3) __block的内存管理
- 当block在栈上时,并不会对__block变量产生强引用。
- 当block被拷贝到堆时:
- 会调用block内部的copy函数;
- copy函数内部会调用
_Block_object_assign
函数; _Block_object_assign
函数会对__block变量形成强引用(retain)。
- 当block从堆中移除时:
- 会调用block内部的dispose函数;
- dispose函数内部会调用
_Block_object_dispose
函数; _Block_object_dispose
函数会自动释放引用的__block变量(release)。
4) 在ARC环境下,编译器会在什么情况下自动将栈上的block复制到堆上?
- block作为函数返回值时;
- 将block赋值给__strong指针时;
- block作为Cocoa API中方法名含有usingBlock的方法参数时;
- block作为GCD API的方法参数时。
5. Block的循环引用问题
1) 为什么Block会产生循环引用?
如果Block是某一对象的成员变量,且内部使用了该对象的其他成员变量。此时Block会对当前对象进行截获变量,Block强引用当前对象,而当前对象又强引用Block,产生自循环引用。可以通过声明对象为__weak来消除自循环引用。
喜欢就支持一下吧
相关推荐