block¶
Block是一个对象,内存结构中是一个结构体。
Block可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。它是对C语言的扩展,用来实现匿名函数的特性。
Block与函数指针的区别¶
- 函数指针只是一个指向函数的地址。
- Block不仅实现函数的功能,还能捕获外部变量(形成闭包)。
block分类¶
1、NSGlobalBlock¶
没有捕获外界变量,或者只用到全局变量、静态(static)变量的block就是全局block。
存储在程序的数据区域(text段)。
不需retain和copy,即使copy,也不会copy到堆区,内存不会发生变化,操作都无效。
void (^globalBlock)(int, int) = ^(int a, int b){
NSLog(@"%d",a+b);
};
NSLog(@"globalBlock:%@",globalBlock);//__NSGlobalBlock__
特点:命长,应用程序在它就在。
对于全局block,用strong或copy修饰都可以。
2、NSStackBlock¶
捕获了外界变量,或者OC的属性,并且赋值给弱引用。
如果想让它获得比stack更久的生命,那就调用Block_copy(),或者copy修饰,拷贝到堆上。
int a = 10;
void (^block)(void) = ^{//copy
//保存一份代码块
NSLog(@"hello %d",a);
};
NSLog(@"block:%@--%@",block,[block copy]);
MRC下:
__NSStackBlock__ __NSMallocBlock__
结果分析:当Block中使用了外部变量,Block为NSStackBlock类型。存储在栈区内存,随栈自生自灭。当函数执行结束后,该Block就会被释放。调用copy后,栈区Block被copy到堆区NSMallocBlock。
ARC下:
__NSMallocBlock__ __NSMallocBlock__
在ARC下,生成的Block默认也是NSStackBlock类型,只是在变量赋值的时候,系统默认对其进行了copy,从NSStackBlock给copy到堆区的NSMallocBlock类型。而在mrc中,则需要手动copy。
3、NSMallocBlock¶
捕获了外界变量,或者OC的属性,并且赋值给强引用
定义的Block要在外部回调使用时,在MRC下,我们需要copy的堆区,永远的持有,不让释放。
在堆区的NSMallocBlock,我们可以对其retain,release,copy(等价于retain,引用计数的加1)。
在ARC下,系统会给我们copy到堆区,避免了很多不必要的麻烦。
引用了外部变量,没经过_Block_copy操作 是栈block,经过block_copy是堆block。
block使用copy¶
- 没有引用临时变量的block是放在global区, 是不会被释放的。
- block引用了栈里的临时变量, 才会被创建在stack区。
- stack区的block只要赋值给strong类型的变量, 就会自动copy到堆里。所以要不要写copy都没关系
block在创建的时候,默认是分配在栈上的。因为栈区中的变量管理是由它自己管理的,随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃问题。
MRC使用copy的目的是将创建默认放在栈区的block拷贝一份到堆区。block放在了堆中,block有个指针指向了栈中的block代码块。
在ARC模式下,系统会默认使用copy进行修饰。
- 如果访问了外部处于栈区的变量(比如局部变量),或处于堆区的变量。都会存放在堆区,如果访问的是内部创建的变量还是存储在全局区。
- 在ARC中做了特殊的处理,自动的做了copy操作,所以为
__NSMallocBlock__在MRC中是__NSStackBlock__。
内存管理¶
Block的内存需要开发人员自己管理,错误的内存管理会造成循环引用内存泄露,或者内存因为提前释放造成崩溃。
Block包含两部分:
- Block所执行的代码,这一部分在编译的时候已经生产好了。
- Block执行时所需要的外部变量值的数据结构,注意的是Block会将使用到的变量的值拷贝到栈上。
循环引用¶
因为Block在拷贝到堆上的时候,会retain其引用的外部变量,那么如果Block中如果引用了他的宿主对象,(也就是定义Block的类)那很有可能引起循环引用,既在自身类的Block中用了self对象,例如:
//循环引用
self.block = ^{
[self function];
};
分析:Block是当前self属性,self强引用Block。当在Block内部捕获了self,Block便强引用了self,两者相互持有,无法释放。
解决方法是ARC 下__weak修饰self:__weak Class *weakSelf = self; MRC下__weak改为__block。
只有双方持有的时候才会造成循环引用。
1. __weak和__strong¶
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"%@",strongSelf);
});
};}
使用了__strong在strongSelf变量作用域结束之前,对weakSelf有一个引用,防止对象(self)提前被释放。而作用域一过,strongSelf不存在了,对象(self)也会被释放。
实例2:
报警告:Capturing 'vc' strongly in this block is likely to lead to a retain cycle。
vc.refreshFuZhu = ^{
[weakSelf refreshFuzhu:vc];
};
警告是因为在 block 中强引用了 vc,可能导致 retain cycle(循环引用)。在 Objective-C 和 Swift 中,block 会捕获并持有其引用的对象。如果 block 中强引用了 vc,而 vc 又持有这个 block,就会导致 retain cycle。
要解决这个问题,可以在 block 中使用弱引用(weak reference)来避免 retain cycle。可以使用 __weak 或 __block 关键字来声明一个弱引用的 vc。
__weak typeof(vc) weakVC = vc;
vc.refreshFuZhu = ^{
__strong typeof(weakVC) strongVC = weakVC;
if (strongVC) {
[weakSelf refreshFuzhu:strongVC];
}
};
在这个修改后的代码中:
- 使用
__weak关键字创建一个弱引用weakVC。 - 在 block 内部,使用
__strong关键字重新创建一个强引用strongVC,以确保在 block 执行期间vc不会被释放。 - 在调用
refreshFuzhu:方法时,使用strongVC作为参数。
这样可以避免 retain cycle,同时确保在 block 执行期间 vc 不会被释放。
2. 局部变量¶
手动释放
//第二种解决方式:引用一个临时第三者中间变量objc。objc->nil手动释放
__block HHBlockViewController *vc = self;//临时变量vc持有
self.block = ^{
//里面代码复杂的情况。里面有异步 耗时操作
//延长生命周期
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
//因为捕获了vc,所以用完之后需要将vc置空
vc = nil;//self->block->vc->self
});
};
3. 传参¶
//循环引用的原因:因为block里面要用self 作用域之间的通讯 self.name在外部,作用空间在block里面的空间。 作用域和作用域之间的传递可以用参数来传递。
self.block1 = ^(HHBlockViewController *vc){
//里面代码复杂的情况。里面有异步 耗时操作
//延长生命周期
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
//可以通过静态分析或者Product的Profile也可以分析
self.block1(self);
Block对外部变量的存取管理¶
1、局部变量¶
1、基本数据类型¶
Block会copy该局部变量的值,不允许修改。所以即使变量的值在Block外改变,也不影响他在Block中的值。
int a = 100;
void(^block)() = ^{
NSLog(@"%d",a);//打印100
//a = 300;//编译会报错
};
a = 200;
block();
block内部修改外界变量的值直接报错,如果想要修改,可以在int a = 0前面加上关键字__block,此时i等效于全局变量或静态变量
__block int b = 0;
void (^block2) () = ^ {
NSLog(@"b = %d",b); // 输出结果 b = 0;
b = 2;
};
block2();
NSLog(@"b = %d",b); //输出结果 b = 2;
2、指针类型¶
block会copy一份指针并强引用指针所指对象,不能修改指针的指向,但是可以修改指针所指向对象的值。
NSMutableString *str = @"abc".mutableCopy;
void (^block4) () = ^ {
// str = @"def"; 报错
[str appendString:@"def"];
NSLog(@"str = %@",str);
};
str = @"123".mutableCopy;
block4(); //输出结果为 "adbdef"
2、全局变量¶
1、static修饰符的全局变量,静态变量,或者全局属性
因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量.
static int a = 100;
void(^block)() = ^{
a += 100;
NSLog(@"%d",a);//打印300
};
a = 200;
block();
2、基本数据类型:成员变量(实例变量),全局变量
block直接访问变量地址,在block内部可以修改变量的值,并且外部变量被修改后,block内部也会跟着变
self.num = 1;
self.num ++;
void (^block3) () = ^ {
self.num++;
};
block3();
NSLog(@"%d",self.num); //输出结果为 3
3、指针类型: 成员变量(实例变量),静态变量,全局变量
block不会复制指针,但是会强引用该对象,内部可修改指针指向,block会强引用成员属性\变量所属的对象,这也是为什么block内部用到self.xxx或_xxx可能会引起循环引用的原因
static NSString *staticStr = @"abc";
void (^block5) () = ^ {
NSLog(@"staticStr = %@",staticStr);
staticStr = @"def";
NSLog(@"staticStr = %@",staticStr);
};
staticStr = @"123";
block5();
//输出结果为 staticStr = 123 staticStr = def
3、__block修饰的变量¶
对外界进行修改的时候需要加__block,拷贝到堆区
修改外部变量¶
- 默认 Block 捕获的外部变量是值拷贝(不能修改)。
- 使用
__block修饰的变量会被包装成对象(__Block_byref_xxx),从而可以在 Block 内部修改。
block底层原理分析¶
进入该文件目录下:clang -rewrite-objc 文件名.c -o 文件名.cpp,编译成C++。
捕获变量¶
捕获对象 如何保存和释放 keep-dispose
1、不加__block¶
int main1(){
//如果要修改a的值 要在前面加__block
__block int a = 10;
void(^block)(void) = ^{
a++;//如果int a不加__block修饰,但是在里面修改a的值,但是里面的a和外面的a不是同一个a,所以就会代码歧义,报错。加了__block就是指针拷贝。同一内存空间。
printf("Hello hh - %d",a);
};
block();
return 0;
}
int main1(){
int a = 10;
//首先去除类型转换的代码
//__main1_block_impl_0 函数
//参数1:block的实现函数__main1_block_func_0
//参数2:__main1_block_desc_0_DATA
//参数3:a
void(*block)(void) = (__main1_block_impl_0(__main1_block_func_0, &__main1_block_desc_0_DATA, a));
//去除类型强转 下面一行可以简写:block->FuncPtr(block)
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
//第一个参数__main1_block_func_0就是下面的方法,里面就是block里面的代码 所以block能够保存代码。
//block代码块 是一个函数
static void __main1_block_func_0(struct __main1_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy 值拷贝 生成了一个新的变量a,值相同,地址不同。
printf("Hello hh - %d",a);
}
static void __main1_block_copy_0(struct __main1_block_impl_0*dst, struct __main1_block_impl_0*src) {
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main1_block_dispose_0(struct __main1_block_impl_0*src) {
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __main1_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main1_block_impl_0*, struct __main1_block_impl_0*);
void (*dispose)(struct __main1_block_impl_0*);
} __main1_block_desc_0_DATA = { 0, sizeof(struct __main1_block_impl_0), __main1_block_copy_0, __main1_block_dispose_0};
block捕获外界变量,把变量的值传了进去,编译的时候block底层会生成了一个新的同名成员变量。。
2、__block原理¶
如果要修改a的值,需要在前面加__block。
__block不能修饰静态变量和全局变量,因为存在全局区。
⽤__block修饰的变量在编译过后会变成__Block_byref__XXX类型的结构体,在结构体内部有⼀个__forwarding 的结构体指针,指向结构体本身。
block创建的时候是在栈上的,在将栈block拷⻉到堆上的时候,同时也会将block中捕获的对象拷⻉到堆上,然后就会将栈上的__block修饰对象的__forwarding指针指向堆上的拷⻉之后的对象。
这样在block内部修改的时候虽然是修改堆上的对象的值,但是因为栈上的对象的__forwarding指针将堆和栈的对象链接起来。因此就可以达到修改的⽬的。
int main1() {
//__block修饰的变量
//先声明一个a 的结构体,对a赋值。结构体a有了外部int a的值和地址空间。
//对结构体赋值 初始化
//int a在栈,结构体a在堆区。
//结构体初始化
__Block_byref_a_0 a = { //假象 拷贝到堆
(void*)0,
(__Block_byref_a_0 *)&a, //地址空间 取a的地址
0,
sizeof(__Block_byref_a_0),
10//a的值
};
void(*block)(void) = ((void (*)())&__main1_block_impl_0((void *)__main1_block_func_0, &__main1_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));//数字570425344是一个flag
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
//__Block_byref_a_0是一个结构体
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;//__forwarding指针指向结构体本身(栈或堆上的副本)
int __flags;
int __size;
int a; // 原始变量
};
static void __main1_block_func_0(struct __main1_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref 指针拷贝 指向同一个内存空间
printf("Hello hh - %d",(a->__forwarding->a));
}
- 通过
__forwarding指针保证无论变量在栈还是堆上,都能正确访问。
block是struct¶
//__block_impl结构体
struct __block_impl {
void *isa; //指向 Block 的类型(全局/栈/堆)
int Flags; //枚举类型
int Reserved;
void *FuncPtr;// Block 的函数指针
};
//没有用__block修饰a
//a的值传到了__main1_block_impl_0里面
struct __main1_block_impl_0 {
struct __block_impl impl;
struct __main1_block_desc_0* Desc;
int a; //捕获的外部变量a,栈block可以捕获外部变量。
//结构体的构造函数
//void *fp就是第一个参数,fp是一个函数
__main1_block_impl_0(void *fp, struct __main1_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;//block在创建的时候是一个StackBlock
impl.Flags = flags;
impl.FuncPtr = fp; //编程思想:函数式。先保存,在block调用的时候执行。
Desc = desc;
}
};
//用__block修饰a
struct __main1_block_impl_0 {
struct __block_impl impl;
struct __main1_block_desc_0* Desc;
__Block_byref_a_0 *a; // byref 用__block修饰
//结构体的构造函数
__main1_block_impl_0(void *fp, struct __main1_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;//保存block函数实现
Desc = desc;
}
};
在 Block 的底层实现中,__Block_byref_xxx 是一个自动生成的结构体名称,byref 是 by reference 的缩写,表示这个结构体用于实现通过引用去捕获变量的机制。
block底层copy处理¶
block copy 从栈copy到堆
通过汇编 查看 block走到了objc_retainBlock。
注:使用__weak修饰的时候,不会调用objc_retainBlock。
objc_retainBlock¶
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
_Block_copy¶
_Block_copy在lib system_blocks.dylib库libclosure-master
// 重点提示: 这里是核心重点 block的拷贝操作: stack栈Block -> malloc堆Block
// 如果原来就在堆上,将引用计数加 1,返回 block 本身;
// 如果 block 在全局区,不加引用计数,也不拷贝,返回 block 本身
// 如果原来在栈上:
// 1.malloc在堆上开辟内存
// 2.memmove会拷贝到堆上
// 3.引用计数初始化为 1
// 4.调用 copy helper 方法(如果存在的话);
// 5.isa标记堆block
// 参数 arg 就是 Block_layout 对象,
// 返回值是拷贝后的 block 的地址
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
// 如果 arg 为 NULL,直接返回 NULL
if (!arg) return NULL;
// 强转为 Block_layout 类型
aBlock = (struct Block_layout *)arg;
const char *signature = _Block_descriptor_3(aBlock)->signature;
// 如果现在已经在堆上
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
// 就只将引用计数加 1
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 如果 block 在全局区,直接返回 block 本身
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {//编译期:栈block,现在需要将其拷贝到堆上
// 在堆上重新开辟一块和 aBlock 相同大小的内存
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
// 开辟失败,返回 NULL
if (!result) return NULL;
// 将 aBlock 内存上的数据全部复制新开辟的 result 上
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
// 将 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清为 0
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
// 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
//操作成员变量
// copy 方法中会调用做拷贝成员变量的工作
_Block_call_copy_helper(result, aBlock);
// 最后设置 isa 指向 _NSConcreteMallocBlock
result->isa = _NSConcreteMallocBlock;//isa标记堆block
return result;
}
}
_Block_call_copy_helper¶
对成员变量拷贝,浅拷贝。
// 调用 block 的 copy helper 方法,即 Block_descriptor_2 中的 copy 方法
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
// 取得 block 中的 Block_descriptor_2
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
// 如果没有 Block_descriptor_2,就直接返回
if (!desc) return;
// 调用 desc 中的 copy 方法,copy 方法中会调用 _Block_object_assign 函数
(*desc->copy)(result, aBlock); // do fixup
}
_Block_object_assign¶
_Block_object_assign 在 Block 被复制到堆时调用,用于处理 Block 捕获的各种类型变量的内存管理。例 是否有加__block
// 注释: Block 捕获外界变量的操作
// 当 block 和 byref 要持有对象时,它们的 copy helper 函数会调用这个函数来完成 assignment,
// destArg: 二级指针,指向要赋值的真正的目标指针位置
// object: 要处理的被捕获对象
// flags: 标志位,表示对象的类型和内存管理方式
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT: // 捕获的对象
/*******
id object = ...;
[^{ object; } copy];
********/
// 默认什么都不干,但在 _Block_use_RR() 中会被 Objc runtime 或者 CoreFoundation 设置 retain 函数,
// 其中,可能会与 runtime 建立联系,操作对象的引用计数什么的
// 增加引用计数(在启用ARC时可能为空操作)
_Block_retain_object(object);
// 使 dest 指向的目标指针指向 object,浅拷贝
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK: // 捕获的Block
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
// 使 dest 指向的拷贝到堆上object
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:// 弱引用 __block 变量
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
// 使 dest 指向的拷贝到堆上的byref
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
// 使 dest 指向的目标指针指向 object
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
// 使 dest 指向的目标指针指向 object
*dest = object;
break;
default:
break;
}
}
_Block_byref_copy¶
__block修饰的(除了对象和block)会走_Block_byref_copy
// 注释: __Block 捕获外界变量的操作 内存拷贝 以及常规处理
// 1. 如果 byref 原来在栈上,就将其拷贝到堆上,拷贝的包括 Block_byref、Block_byref_2、Block_byref_3,
// 被 __weak 修饰的 byref 会被修改 isa 为 _NSConcreteWeakBlockVariable,
// 原来 byref 的 forwarding 也会指向堆上的 byref;
// 2. 如果 byref 已经在堆上,就只增加一个引用计数。
// 参数 dest是一个二级指针,指向了目标指针,最终,目标指针会指向堆上的 byref
static struct Block_byref *_Block_byref_copy(const void *arg) {
// arg 强转为 Block_byref * 类型
struct Block_byref *src = (struct Block_byref *)arg;
// 判断条件:引用计数为 0,说明变量在栈上。栈上变量 -> 堆上拷贝
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// 为新的 byref 在堆中分配内存
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// 新的 byref 设置标志flags:
// BLOCK_BYREF_NEEDS_FREE: 标记需要释放(即标识堆上)
// 4: 设置引用计数为 2(实际是 2,因为低2位是标志位)
// byref value 4 is logical refcount of 2: one for caller, one for stack
// 为什么是 2 呢?注释说的是 non-GC one for caller, one for stack
// one for caller 很好理解,那 one for stack 是为什么呢?
// 看下面的代码中有一行 src->forwarding = copy。src 的 forwarding 也指向了 copy,相当于引用了 copy
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
//更新 forwarding 指针:
// 堆上 byref 的 forwarding 指向自己
copy->forwarding = copy; // patch heap copy to point to itself
// 原来栈上的 byref 的 forwarding 现在也指向堆上的 byref
src->forwarding = copy; // patch stack to point to heap copy
// 拷贝 size
copy->size = src->size;
// 处理辅助函数
// 如果 src 变量有自定义的 copy/dispose helper 函数(如对象类型),调用 byref_keep 来执行特定的内存管理
// 展布局信息(用于 ARC 弱引用等)
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
// 取得 src 和 copy 的 Block_byref_2
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
// copy 的 copy/dispose helper 也与 src 保持一致
// 因为是函数指针,估计也不是在栈上,所以不用担心被销毁
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
// 如果 src 有扩展布局,也拷贝扩展布局
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
// 没有将 layout 字符串拷贝到堆上,是因为它是 const 常量,不在栈上
copy3->layout = src3->layout;
}
// 调用 copy helper,因为 src 和 copy 的 copy helper 是一样的,所以用谁的都行,调用的都是同一个函数
//捕获到了外界变量 进行相关内存处理 生命周期的保存
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
// 如果 src 没有 copy/dispose helper
// 将 Block_byref 后面的数据都拷贝到 copy 中,一定包括 Block_byref_3
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// src 已经在堆上,就只将引用计数加 1
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
block不能修改外部变量指针地址。
内存布局示例¶
栈上的 Block_byref:
[ flags | forwarding | size | data... ]
↓
堆上的 Block_byref copy:
[ flags | forwarding | size | data... ]
↑
实际使用场景¶
// 示例代码
__block int counter = 0;
__block NSObject *obj = [[NSObject alloc] init];
void (^myBlock)(void) = ^{
counter++; // 修改 __block 变量
NSLog(@"%@", obj);
};
// 当 Block 被复制到堆时,_Block_byref_copy 会被调用
// 确保 counter 和 obj 在栈帧销毁后仍然有效
__block修饰变量的关键点¶
- forwarding 指针:保证无论访问栈还是堆上的变量,最终都访问堆上的结构体。
- 延迟复制:block创建的时候在栈上,只在 Block 被拷贝到堆时才触发将变量一起拷贝到堆。
- 引用计数管理:堆上的 byref 有独立引用计数
- 类型支持:通过 copy/dispose 函数支持 Objective-C 对象等复杂类型
这是 Block 实现的重要部分,实现了 __block 变量的自动内存管理。
block结构¶
block本质是Block_layout结构体
block其实是一个对象,需要一个初始化过程。
//block在底层 结构体
struct Block_layout {
void *isa; //isa指向(栈block、堆block、全局block),有isa说明block也是一个对象
/**
Flags 枚举类型
BLOCK_HAS_COPY_DISPOSE:捕获外界变量的时候 这个是有值的。
BLOCK_HAS_SIGNATURE:签名
方法有签名v@: 普通签名
v 返回值
@ 对象
: cmd方法编号
block也有签名信息
*/
//存储block附加信息
volatile int32_t flags; // contains ref count//标识符 存数据信息,是否正在析构、是否有keep函数、是否有析构函数、等等
int32_t reserved;//保留的变量(暂时不用)
BlockInvokeFunction invoke;//block实现的函数指针
// block其它描述信息:存储copy、dispose函数、block大小、block描述信息、捕获外界信息、block方法签名
struct Block_descriptor_1 *descriptor;
//连续的内存空间
// imported variables //还有可选变量
};
Block_descriptor_1¶
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved; // 保留字段,通常为0
uintptr_t size; // Block 的总大小(包括所有部分),用于内存分配和释放
};
Block_descriptor_2¶
//捕获int变量时,没有copy和dispose。捕获对象才有。
//copy和dispose函数是⽤来对block内部的对象进⾏内存管理的,block拷⻉到堆上会调⽤copy函数,在block从堆上释放的时候会调⽤dispose函数。
//内存平移Block_descriptor_1大小就得到Block_descriptor_2
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy; //当 Block 被复制到堆时调用,处理捕获变量的内存管理
BlockDisposeFunction dispose; //当 Block 从堆中释放时调用,清理捕获的资源
};
何时存在? 当 Block 捕获了需要特殊内存管理的对象时:
- Objective-C 对象(MRC 下)
- 其他 Block
__block变量- C++ 对象等
示例代码
// 捕获 NSString 对象
NSString *str = @"Hello";
void (^block)(void) = ^{
NSLog(@"%@", str); // 需要管理 str 的内存
};
// 编译器生成的描述符
static void __main_block_copy_0(struct __main_block_impl_0 *dst,
struct __main_block_impl_0 *src) {
// 处理 str 的 retain
_Block_object_assign(&dst->str, src->str, BLOCK_FIELD_IS_OBJECT);
}
static void __main_block_dispose_0(struct __main_block_impl_0 *src) {
// 处理 str 的 release
_Block_object_dispose(src->str, BLOCK_FIELD_IS_OBJECT);
}
// Descriptor2
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_descriptor_3¶
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature; // Block 的方法签名(类型编码),包含参数和返回值类型
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
// hook:invoke消息 消息机制 转发需要先获取签名 然后invocation处理
何时存在? 当需要额外的运行时信息时:
- 有方法签名需求(如用 Block 实现 delegate)
- 捕获了弱引用对象
- 复杂的捕获变量布局
示例:Block 类型编码¶
// Block: int (^)(int, NSString *)
signature = "i32@?0i8@\"NSString\"16"
// 解析:
// i32 - 返回值 int (32位)
// @? - 这是一个 Block 类型,block签名是 @?
// 0 - 从偏移 0 开始
// i8 - 第一个参数 int
// @"NSString"16 - 第二个参数 NSString*,从偏移16开始
实际使用场景:¶
场景1:无捕获¶
void (^block)(void) = ^{ NSLog(@"Hello"); };
// 只需要 Descriptor1(size)
场景2:捕获对象¶
NSObject *obj = [[NSObject alloc] init];
void (^block)(void) = ^{ NSLog(@"%@", obj); };
// 需要 Descriptor1 + Descriptor2(copy/dispose)
场景3:捕获弱引用¶
__weak NSObject *weakObj = obj;
void (^block)(void) = ^{ NSLog(@"%@", weakObj); };
// 需要 Descriptor1 + Descriptor2 + Descriptor3(layout)
场景4:有类型签名的 Block¶
int (^block)(int, int) = ^int(int a, int b) { return a + b; };
// 需要 Descriptor1 + Descriptor3(signature)
捕获变量¶
三层拷贝:
- _Block_copy栈block拷贝到堆block
- block捕获Block_byref_ 对Block_byref_进行copy
- Block_byref对object进行copy