概述
在 iOS 开发中,Block
是一种非常重要的编程概念,它能够将代码块作为参数传递和回来,从而便利实现回调、异步任务和代码封装等功能。在本文中,我们将深入探讨 Block
的技术细节,包括 Block
的界说、分类、内存管理、循环引证及其处理方案等
界说
Block
是一种由苹果引入的 Objective-C
扩展,是一种代码块目标,能够像普通目标相同存储到变量中,也能够作为参数传递和回来。其本质是一个封装函数调用以及函数调用环境的OC目标,其内部也有个isa
指针在 Objective-C
中,Block
有三种类型:大局 Block
、栈 Block
和堆 Block
大局 Block
:大局 Block
是指它的效果域在整个应用程序中都能够拜访,因为它存储在大局数据区,而且体系会在应用程序启动时主动创立。
栈 Block
:栈 Block
是指它的效果域只在当时函数内部,当它超出效果域时,它会主动被毁掉。栈 Block
只能拜访当时函数的变量,不能拜访大局变量和静态变量。
堆 Block
:堆 Block
是指它的效果域在整个应用程序中都能够拜访,因为它被存储在堆内存中。当堆 Block
被赋值给一个变量时,该变量会对其进行引证计数,而且在没有任何引证时,堆 Block
会主动被毁掉。
源码分析
struct __block_impl {
void *isa; // isa指针,指向一个类目标,有三种类型:
int Flags; // block 的负载信息(引证计数和类型信息),按位存储
int Reserved; // 保存变量
void *FuncPtr; // 一个指针,指向Block执行时调用的函数,也便是Block需求执行的代码块
};
struct block_descriptor {
size_t reserved; // Block版别升级所需的预留区空间,在这里为0。
size_t Block_size; // Block巨细(sizeof(struct __blockTest_block_impl_0))
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct block_descriptor* descriptor;
...
// 构造函数
__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;
}
};
变量捕获(capture)
为了保证block
内部能够正常拜访外部变量,block
有个变量捕获机制
block内部拜访目标类型的auto变量
假如block是在栈上,将不会对auto变量发生强引证block拷贝到堆上的进程
-
调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数
-
_Block_object_assign函数会依据auto变量的润饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,构成强引证(retain)或者弱引证
// __block变量 变量名为a _Block_object_assign((void*)&dst->a, (void*)src->a, 8/BLOCK_FIELD_IS_BYREF/);
// __block目标类型的auto变量 变量名为p _Block_object_assign((void*)&dst->p, (void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/);
block从堆上移除的进程
- 调用
block
内部的dispose
函数,dispose
函数内部会调用_Block_object_dispose
函数 -
_Block_object_dispose
函数会主动开释引证的auto
变量(release)
定论如下
- 假如
block
在栈空间,不论外部变量是强引证还是弱引证,都不会对变量发生强引证 - 假如
block
在堆空间,假如外部强引证,block
内部也是强引证;假如外部弱引证,block
内部也是弱引证
__block润饰符
界说:__block
会将变量包装成一个OC目标
效果:用于处理block内部无法修正auto变量值的问题
__block int age = 10
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;//age的地址
int __flags;
int __size;
int age;//age 的值
};
注意:
- __block不能润饰大局变量、静态变量
- __Block_byref_age_0结构体内部地址和外部变量age是同一地址
block内部拜访__block润饰的目标类型
假如block是在栈上,将不会对指向的目标发生强引证当__block变量被copy到堆时
- 会调用
__block
变量内部的copy
函数 -
copy
函数内部会调用_Block_object_assign
函数 -
_Block_object_assign
函数会依据所指向目标的润饰符(__strong
、__weak
、__unsafe_unretained
)做出相应的操作,构成强引证(retain
)或者弱引证(注意:这里仅限于ARC
时会retain
,MRC
时不会retain
)
假如__block变量从堆上移除
- 会调用
__block
变量内部的dispose
函数 - dispose函数内部会调用
_Block_object_dispose
函数,_Block_object_dispose
函数会主动开释指向的目标(release
)
处理循环引证问题 – ARC
1、用__weak
、__unsafe_unretained
处理
__weak typeof(self) weakSelf = self;
// __unsafe_unretained id weakSelf = self;
self.block = ^{
NSLog(@"%p", weakSelf);
}
2、用__block处理(必需要调用block,而且置目标为nil)
__block id weakSelf = self;
self.block = ^{
NSLog(@"%p", weakSelf);
weakSelf = nil;
}
self.block();
处理循环引证问题 – MRC
1、用__weak、__unsafe_unretained处理
// 因为MRC环境下,不支持__weak
__unsafe_unretained id weakSelf = self;
self.block = ^{
NSLog(@"%p", weakSelf);
}
2、用__block处理
__block id weakSelf = self;
self.block = ^{
NSLog(@"%p", weakSelf);
}