Block的本质透析
block的底层结构
- 底层结构
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; // 函数地址
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; // block的内存占用大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
-
block的内存布局
WeChatff95bfc5e1541d5dc207875b212ab3d0.png
Block的变量捕获
image.png int a = 10; // auto变量 值拷贝
static int b = 10; // static修饰的局部变量 称之为静态局部变量 存储在静态存储区, 它的生命周期是整个应用程序,一直可以访问到 所以是 指针拷贝 (&b);
void (^block)(void) = ^{
NSLog(@"%d, %d", a, b);
};
a = 20;
b = 20;
block();
// 底层会变成这样
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; // 数值传递
int *b; // 指针传递 直接指向了外部的b的地址 所以可以直接修改
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 局部变量之所以会被捕获是因为, 可能会被跨函数访问, 所以局部变量需要被存起来, 那么存在哪里呢? 那肯定是block内部啊, 所以block会捕获它们, 它以后得用, 不然用的时候找不到了, 那不是出问题了吗
- 全局变量, 静态全局变量不会被捕获, 因为在哪一个函数都能访问到它们
block的类型
image.png-
NSGlobalBlock
: 没有使用到auto变量 (static变量 全局变量也还是GlobalBlock类型)
NSStackBlock
: 访问了auto变量MRC下显示NSStackBlock ARC下编译器会自动对Stackblock进行copy 所以是NSMallocBlock
NSStackBlock在栈区, 一旦出了作用域就会被销毁, block内部的数据就会是垃圾数据,为了保证安全
ARC下
编译器帮我们对strong强引用
的对象自动调用了copy
, 将其拷贝到堆区, 保证它的调用安全,所以拷贝到堆区后会显示NSMallocBlock
-
NSStackBlock
一旦调用copy, 就会变成NSMallocBlock
image.png
捕获对象类型的auto变量
WTBlock block = NULL;
{
__strong WTPerson *p = [WTPerson new]; // 当变量的所有权修饰符是 strong时, 不写默认就是strong
__weak WTPerson *weakPerson = p;// 如果修饰符是 __weak
p.name = @"123";
block = ^{
NSLog(@"%@",p.name);
};
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
WTPerson *__strong p; // block内部就会以strong的形式持有访问的变量
WTPerson *__weak weakPerson; // block内部就会以weak的形式持有访问的变量
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
总结
堆上的block
捕获auto变量
会连同对象的 所有权修饰符一起捕获, 如果捕获的是对象类型,desc结构体
会新增2个函数copy
和dispose
, 用于捕获对象的内存管理
-
当
block
在栈上
, 访问了对象类型的auto变量
, 是不会对auto变量产生强引用
, 因为block在栈上, 它自身都随时可能被销毁, 所以没法保证访问的变量的安全, 所以不会产生强引用 -
如果block被拷贝到堆上, 并且捕获了
对象类型
desc
的结构体内会新增2个函数指针copy
和dispose
copy
内部会调用Block_object_assign
函数
Block_object_assign
函数会根据捕获
的auto变量的所有权修饰符
(__strong, __weak, unsafe_unretained
)做出相应的内存管理操作, 形成强引用(retained)
或弱引用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
- 如果block从堆上移除
会调用
dispose
函数, dispose内部会调用Block_object_dispose
来释放引用的auto变量(类似release
)
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
__block的本质
-
__block
会将修饰的变量包装成一个对象
- block结构体会新增
__Block_byref_age_0
结构体(ByRef 按地址传递)
WeChat26722aced47e2c5a1e4489369c943b9d.png
__block的内存管理
-
当block在栈上时, 并不会对
__block
变量产生强应用 -
当block被copy到堆上时, (
ARC对强引用指向的对象会自动拷贝到堆上
)
1 会调用block
中desc结构体
的copy
函数
2copy
函数内部调用Block_object_assign
函数
3Block_object_assign
会对__block
包装的变量形成强引用
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
}
- 一开始我们的代码是这样
__block int age = 10;
WTBlock block = ^{
age = 20;
};
block();
刚开始
image.pngblock
和age
都是在栈上, 当block
被强引用指向时, 会被编译器copy
拷贝到堆上时, 也会同时将捕获的__block变量
也拷贝到堆上, 并且对__block变量
进行强引用
- 当block从堆中移除的时候
1 block内部会调用dispose
函数
2dispose
函数会调用Block_object_dispose
释放__block变量
(release
)
WeChat665e4ef4cdd5bf979ab0da0d3047af32.png
图左
block
持有__block变量
当block
被销毁时,block
调用dispose
函数 将__block
变量进行release
操作
图右block0 和block1
同时引用__block变量
, 当block0
被销毁时, 对__block变量
进行release
操作, 它的引用计数-1, 当block1
也被销毁时, 也对__block变量
进行release
操作, 它的引用计数为0, 最终没有持有者而被销毁
对象类型的auto变量、__block变量
-
当block在栈上时, 对它们不会产生强引用
-
当block在堆上时, 会调用copy函数来处理他们
1__block
变量 (基本数据类型a)
_Block_object_assign(&dst->a), src->a, 8)
BLOCK_FIELD_IS_BYREF
block
对__block包装的对象
直接就是强引用
2 对象类型的auto变量(p)
_Block_object_assign(&dst->p, src->p, 3)
BLOCK_FIELD_IS_OBJECT
会根据对象的所有权修饰符
, 来决定是强引用
还是弱引用
-
当block从堆上移除时, 都会通过dispose函数来释放它们
WeChat84cb55937b4fa3bc2997d2b4fac85a90.png
1 __block_Block_object_dispose(src->a, 8)
BLOCK_FIELD_IS_BYREF
2 auto变量_Block_object_dispose(src->p, 3)
BLOCK_FIELD_IS_OBJECT
__block 修饰对象类型的auto变量
- 首先block的结构体还是会新增一个
__Block_byref_person_0
这个和修饰基本数据类型的时候保持一致
__block WTPerson *person = [WTPerson new];
person.name = @"123";
WTBlock block = ^{
NSLog(@"%@", person.name);
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_person_0 *person; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 但是发现
__Block_byref_person_0
内发生了改变
1 新增了copy
和dispose
函数
2 也会有一个同类型
的成员变量, 并且所有权修饰符
和外面一致
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
WTPerson *__strong person; // 如果外面申明的时候使用的是weak 那么这里就是WTPerson *__weak person
};
当
block
被拷贝到堆区时,block
内部会调用copy
函数 将__Block_byref_person_0
也拷贝到堆区,并对它产生强引用
当__Block_byref_person_0
被拷贝到堆区时,__Block_byref_person_0
也会调用其内部的copy函数 对WTPerson *__strong person
进行强引用或者弱引用(这个和对象的所有权修饰符有关) (只有ARC下才会强引用,MRC下一直都是弱引用
)
当
block
被销毁时,block
会调用dispose
函数, 释放__Block_byref_person_0
(release)
__Block_byref_person_0
被销毁时候, 也会调用内部的dispose
函数, 对WTPerson *__strong person
进行一次释放操作
__Block_byref_person_0 中的 forwarding
-
forwarding
指针指向哪里?
ARC下 forwarding不论是栈上还是堆上var->forwarding->var
都是指向堆上的var
MRC下forwarding 指向堆上的var
那这个说明了
image.pngforwarding
是为了让我们更好的管理内存的,不论现在block是出于栈中
还是堆中
,都不会影响到寻找到的相关信息,当block是在栈中,__forwarding指向的就是栈本身的地址
,当block copy到堆中的时候,__forwarding指针指向的就是堆本身的地址
Block的循环引用问题
-
__weak
不会产生强引用,指向的对象销毁时, 会将指针置为nil -
__unsafe_unretaind
不会产生强引用, 不安全, 指向的对象销毁时, 指针存储的地址值不变 -
__block
(必须调用block, 必须要清空 __block包装的对象) NSProxy中间类
__block WTPerson *person = [WTPerson new];
person.block = ^{
NSLog(@"%@", person.name);
person = nil;
};
person.block();
image.png
Block相关面试题
1.
block的本质是什么?
block是封装了函数调用和函数调用环境的OC对象
__block的作用是什么? 有什么使用注意点
- __block会将修饰的对象或者基本数据类型,包装成一个对象(结构体), 对象被block强引用
- 它可以解决block内部无法修改auto变量值的问题
- 注意点 内存管理方面, 在MRC下 包装的对象对OC对象是不会产生强引用
block的属性修饰词为什么是copy, 使用block有哪些注意点
- block如果没有进行copy就不会再堆上, 可能是在数据区(data区)NSGlobalBlock, 也可能是在栈区NSStackBlock,它的生命周期不受我们管控,为了延长它的生命周期, 使用copy将它拷贝到堆区, 从而来管理它的生命周期, 方便我们能够安全的使用
使用注意: 循环引用问题
block内修改NSMutableArray, 需不需要加__block
使用NSMutableArray add remove等 操作时候不需要加
如果改变NSMutableArray指针的指向就需要加__block, 因为NSMutableArray是auto变量, 此时需要使用__block修饰符来允许对变量进行修改
使用__weak后, 为什么还需要在block内部使用__strong
这是为了保证在block内部使用时, 用strong来强引用weak指向的弱指针, 避免在block执行时已经被释放掉了.可以保证在 Block 执行期间对象不会被提前释放。这样可以确保在 Block 内部安全地使用该对象,