本章开始分析“耳熟能详”的
Block
,介绍block
的类型,block
的循环引用的几种解决方式以及block
底层如何操作。💪 Let's Go
0x00 - block
的类型
通过打印,来看block
有几种类型
__NSGlobalBlock__
普通block
,没有使用外部数据,是存在全局
的block
__NSMallocBlock__
使用外部变量, 会变改变block
的存放位置,称为堆区block
,因为block既是函数,也是对象,底层会copy a
__NSStackBlock__
其中局部变量a在没有处理之前(即没有拷贝之前)是 栈区block
, 处理后(即拷贝之后)是堆区block
,目前的栈区block越来越少了,⚠️⚠️当前Xcode为11.6
iOS 13.6,如果是Xcode12 iOS14之后,打印输出是NSMallocBlock
还可以是使用__weak
不强持有, block
也是在栈区的
Xcode12 iOS14 环境打印
总结
block
不访问外界变量,直接放在全局区
- 如果有使用外部数据,就会对
block
进行相应的copy
,- 如果
block
是强引用, 此时的block
在堆区 - 如果用
__weak
修饰block
,此时block
在栈区
- 如果
0x01 - 循环♻️引用
在开发中使用block
最常遇到的就是循环引用的问题
- 正常释放 : 在对象A持有对象B的时时候,对象A释放会调用
dealloc
方法,然后会给对象B发送release
信号, 对象B收到信号后,如果此时对象B的引用计数为0, 就会调用对象B自己的dealloc
方法,释放自己。
- 循环引用: 对象A和对象B互相持有, 所以导致对象A释放不了自己, 给对象B发送不了
release
信号,对象B收不到信号,也释放不了自己,一直占用这内存。也叫内存泄露
0x02 - 循环♻️引用的解决方式
有了循环引用
的问题,就要寻求解决方式:
首先看如下俩段代码,会发声循环引用吗?
//代码一
NSString *name = @"cc";
self.block = ^(void){
NSLog(@"%@",self.name);
};
self.block();
//代码二
UIView animateWithDuration:1 animations:^{
NSLog(@"%@",self.name);
};
- 很明显的看出
代码段1
发生了循环引用的问题,因为在block
内存使用了self
,导致block
持有self
,而self
也持有block
,所以导致了相互的持有, 无法释放。 代码段2
中没有发生循环引用的问题, 因为self
本身没有持有animation block
,不构成相互持有的关系。
解决方式:
weak-strong-dance
typedef void(^testBlock)(void);
@property(nonatomic, copy) testBlock tBlock;
__weak typeof(self) weakSelf = self;
self.tBlock = ^(void){
NSLog(@"%@",weakSelf.name);
};
如果block
内部并没有嵌套block
,直接使用__weak
修饰,此时的weakSelf
指向self
的那片区域,但并没有改变那片内存的一个引用计数,weakSelf
只是保持对那片内存区域的一个弱引用
的关系。
上一种只使用单_weak
解决,在实际项目中,还是有一丢丢问题,在某些场景下容易提前释放,比如:
// VC2:
__weak typeof(self) weakSelf = self;
self.tBlock = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",weakSelf.name);
});
};
如上代码段,在vc2 有这么一段代码, 在block
里2秒之后执行输出, 从上一个VC1
跳到VC2
,还没等延迟的输出代码
执行,我就返回了上个页面。
加上__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf
是block
内部的一个临时变量,内部block
执行完毕, 随着作用域也就释放了,
这种方式属于打破self对block的强引用
,依赖于中介者模式
,属于自动置为nil,即自动释放
__block
修饰变量方式
这种方式也是依赖中介者模式
,只不过由自动
变为手动
self.name = @"cccc";
__block ViewController *vc = self;
self.tBlock = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil; // 手动释放
});
};
self.tBlock();
需要注意的是这里的block必须调用
,如果不调用block,vc就不会置空,那么依旧是循环引用,self和block都不会被释放
- 对象本身作为参数
typedef void(^testBlock)(ViewController *);
@property(nonatomic, copy) testBlock tBlock;
self.name = @"cccc";
self.tBlock = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.tBlock(self);
主要是将对象self作为参数,提供给block内部使用,不会有引用计数问题
NSProxy
虚拟类
OC
只能单继承,基于运行时机制
,可以通过NSProxy
实现伪多继承
,填补了多继承的空白NSProxy
是和NSObject
同一个级别的类,它也实现了NSObject
协议。NSProxy
其实是一个消息重定向封装的一个抽象类
,类似一个代理人,中间件,可以通过继承它,并重写下面两个方法来实现消息转发到另一个实例
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
使用场景
- 实现多继承功能
- 解决了
NSTimer&CADisplayLink
创建时对self强引用
问题,参考YYKit
的YYWeakProxy
。
场景展示
- 自定义一个
NSProxy
的子类
@interface MyProxy : NSProxy
// 俩个初始化方法,保存外部的一个对象
- (id)transformObjc:(NSObject *)objc;
+ (instancetype)proxyWithObjc:(id)objc;
@end
@interface MyProxy ()
@property(nonatomic, weak, readonly) NSObject *objc;
@end
@implementation MyProxy
- (id)transformObjc:(NSObject *)objc{
_objc = objc;
return self;
}
+ (instancetype)proxyWithObjc:(id)objc{
return [[self alloc] transformObjc:objc];
}
#pragma mark -
//2.有了方法签名之后就会调用方法实现
- (void)forwardInvocation:(NSInvocation *)invocation {
SEL sel = [invocation selector];
if ([self.objc respondsToSelector:sel]) {
[invocation invokeWithTarget:self.objc];
}
}
//1、查询该方法的方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
NSMethodSignature *signature;
if (self.objc) {
signature = [self.objc methodSignatureForSelector:sel];
} else {
signature = [super methodSignatureForSelector:sel];
}
return signature;
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [self.objc respondsToSelector:aSelector];
}
@end
- 通过
NSProxy
子类化实现多继承
功能
- 通过
MyProxy
解决定时器中self的强引用
问题
self.timer = [NSTimer timerWithTimeInterval:1 target:[MyProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
总结
循环引用的打破从本质上讲就俩种, 以self ---> block ----> self
举例子
- 打破
self---->block
的强引用, 使用weak
修饰属性, 但是这样的话,block
还没有创建玩就被释放了, 所以这里走不通 - 打破
block------>self
这层的强引用关系,weak-strong
__block修饰,内部置空,必须调用block
- 将对象
self
作为参数 - 通过
NSProxy
的方式替代self
0x03 -Block
底层分析
通过
Clang
、断点调试、汇编等方式分析底层逻辑结构
先写一个block
文件, 再使用Clang rewrite
一下。
#include "stdio.h"
int main(){
__block int a = 10;
char c = 'b';
void(^block)(void) = ^{
printf("test block %d %c", a, c);
a+=1;
};
block();
return 0;
}
使用xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c
生成一个block.cpp
的文件,block
在底层被编译成了以下形式
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("test block");
}
int main(){
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
//******简化******
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));//构造函数
block->FuncPtr(block);//block调用执行
- 首先查看
__main_block_impl_0
,一个结构体类型, 说明block
也是一个对象,也是为什么block
可以使用%@
打印的原因。而且没有引用外部变量,它的impl.isa = &_NSConcreteStackBlock;
,是栈区的block(这里是编译时,运行时会copy成为堆块)。
//**block代码块的结构体类型**
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 flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//**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;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
总结:block
的本质
是对象、函数、结构体
,由于block函数没有名称,也被称为匿名函数
,和c/c++
的函数指针
很像
Clang 编译的底层源码结构关系图
1. block
为什么需要调用?
在底层block
被编译成一个struct __main_block_impl_0
的结构体,在初始化的时候,
第一个参数是block
需要执行的代码块,即{........}
花括号的的函数地址。就是*fp
,赋值给impl.FuncPtr = fp
然后在main中进行调用block->FuncPtr
调用
函数声明
:即block内部实现声明成了一个函数__main_block_func_0
执行具体的函数实现
:通过调用block的FuncPtr
指针,调用block执行
2. block是如何获取外界变量的?
int main(){
int a = 11;
void(^block)(void) = ^{
printf("tsetBlock - %d", a);
};
block();
return 0;
}
上边的代码段通过Clang
编译到底层为以下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;//编译时就自动生成了相应的变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;//block的isa默认是stackBlock
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// bound by copy 值传递的方式,会把值拷贝一份,即 a = 10,此时的a与传入的__cself的a并不是同一个
int a = __cself->a;
printf("tsetBlock - %d", a);
}
int main(){
int a = 11;
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));
block)->FuncPtr(block);
return 0;
}
__main_block_impl_0
中的a是通过值拷贝的形式存在,如果在内部对a
变,编译器是不允许的,因为此时的a
是只读,会提示使用__block
修饰。
总结:
block捕获外界变量时,在内部会自动生成同一个属性来保存
3 __block
的原理
- 值拷贝
- 深拷贝,只是拷贝数值,且拷贝的值不可更改,指向不同的内存空间,案例中普通变量
a就是
值拷贝指针拷贝
- 浅拷贝,生成的对象指向同一片内存空间,案例中经过__block
修饰的变量a
就是指针拷贝
-
通过以上可知, 在代码中使用
__block
修饰变量a
,通过Clang
编译, 在底层中a
会被封装成一个对象
-
然后在
main
中,会把封装的对象的地址
传给构造函数 -
在
__main_block_func_0
内部对a的处理是指针拷贝
,此时创建的对象a与传入对象的a指向同一片内存空间
struct __Block_byref_a_0 {//__block修饰的外界变量的结构体
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {//block的结构体类型
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {//构造方法
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {//block内部实现
__Block_byref_a_0 *a = __cself->a; // bound by ref 指针拷贝,此时的对象a 与 __cself对象的a 指向同一片地址空间
//等同于 外界的 a++
(a->__forwarding->a)++;
printf("CJL - %d", (a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
int main(){
//__Block_byref_a_0 是结构体,a 等于 结构体的赋值,即将外界变量a 封装成对象
//&a 是外界变量a的地址
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 11};
//__main_block_impl_0中的第三个参数&a,是封装的对象a的地址
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
总结
外部变量
使用__block
修饰会生成一个__Block_byref_a_0
结构体- 结构体用来
保存原始变量的指针和值
- 将变量生成的
结构体对象的指针地址 传递给block
,然后在block内部就可以对外界变量进行操作了
4 Block
底层真正的类型
首先探究下
Block
在底层源码的位置。
- 通过在
Block
处打断点, 开启Always Show Disassembly
看汇编
- 看到有
objc_retainBlock
, 加这个符号断点继续调试
- 加
_Block_copy
符号断点,看调试
在Clang
编译的CPP
静态文件里也有这么一句
可以到苹果开源网站下载最新的libclosure-74源码,通过查看_Block_copy
的源码实现,发现block在底层的真正类型是Block_layout
Block
正真的类型
// Block 结构体
struct Block_layout {
//指向表明block类型的类
void *isa;//8字节
//用来作标识符的,类似于isa中的位域,按bit位表示一些block的附加信息
volatile int32_t flags; // contains ref count 4字节
//保留信息,可以理解预留位置,用于存储block内部变量信息
int32_t reserved;//4字节
//函数指针,指向具体的block实现的调用地址
BlockInvokeFunction invoke;
//block的附加信息
struct Block_descriptor_1 *descriptor;
// imported variables
};
isa
:指向表明block类型的类flags
:标识符,按bit位表示一些block的附加信息,类似于isa中的位域,其中flags的种类有以下几种,主要重点关注BLOCK_HAS_COPY_DISPOSE
和BLOCK_HAS_SIGNATURE
。BLOCK_HAS_COPY_DISPOSE
决定是否有Block_descriptor_2
。BLOCK_HAS_SIGNATURE
决定是否有Block_descriptor_3
- 第1 位 -
BLOCK_DEALLOCATING
,释放标记,-般常用 BLOCK_NEEDS_FREE 做 位与 操作,一同传入 Flags , 告知该 block 可释放。 - 低16位 -
BLOCK_REFCOUNT_MASK
,存储引用计数的值;是一个可选用参数 - 第24位 -
BLOCK_NEEDS_FREE
,低16是否有效的标志,程序根据它来决定是否增加或是减少引用计数位的 值; - 第25位 -
BLOCK_HAS_COPY_DISPOSE
,是否拥有拷贝辅助函数(a copy helper function); - 第26位 -
BLOCK_IS_GC
,是否拥有 block 析构函数; - 第27位,标志是否有垃圾回收;//OS X
- 第28位 -
BLOCK_IS_GLOBAL
,标志是否是全局block; - 第30位 -
BLOCK_HAS_SIGNATURE
,与 BLOCK_USE_STRET 相对,判断当前 block 是否拥有一个签名
。用于 runtime 时动态调用。
- 第1 位 -
// flags 标识
// Values for Block_layout->flags to describe block objects
enum {
//释放标记,一般常用于BLOCK_BYREF_NEEDS_FREE做位与运算,一同传入flags,告知该block可释放
BLOCK_DEALLOCATING = (0x0001), // runtime
//存储引用引用计数的 值,是一个可选用参数
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
//低16位是否有效的标志,程序根据它来决定是否增加或者减少引用计数位的值
BLOCK_NEEDS_FREE = (1 << 24), // runtime
//是否拥有拷贝辅助函数,(a copy helper function)决定block_description_2
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
//是否拥有block C++析构函数
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
//标志是否有垃圾回收,OSX
BLOCK_IS_GC = (1 << 27), // runtime
//标志是否是全局block
BLOCK_IS_GLOBAL = (1 << 28), // compiler
//与BLOCK_HAS_SIGNATURE相对,判断是否当前block拥有一个签名,用于runtime时动态调用
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
//是否有签名
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
//使用有拓展,决定block_description_3
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
reserved
: 保留信息,理解成预留的一些位置,猜测是用于存储block内部变量信息invoke
: 函数指针,指向Block
要执行的函数代码块descriptor
:block
的附加信息,比如保留变量数、block的大小、进行copy或dispose的辅助函数指针。有三类Block_descriptor_1
是必选的Block_descriptor_2
和Block_descriptor_3
是可选的。根据一些条件来配置这俩
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;//保留信息
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;//拷贝函数指针
BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;//签名
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
在runtime.h
中, 从Block_layout
结构中直接获取Block_descriptor_1
,Block_descriptor_2
和Block_descriptor_3
都是通过Block_descriptor_1
的内存平移大小来获取的。
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;//默认打印
}
#endif
//Block 的描述 : copy 和 dispose 函数
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;//descriptor_1的地址
desc += sizeof(struct Block_descriptor_1);//通过内存平移获取
return (struct Block_descriptor_2 *)desc;
}
// Block 的描述 : 签名相关
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
内存变化
void (^block1)(void) = ^{
NSLog(@"testBlock");
};
block1();
如上代码段
block
处的断点读取寄存器的值,此时的block
是全局block
block
内部使用外部变量
int a = 10;
void (^block1)(void) = ^{
NSLog(@"testBlock - %d", a);
};
block1();
断点停到 objc_retainBlock
, 进入之前看一下内存的值类型,现在是栈block
进入objc_retainBlock
后,再打印下类型,依旧是栈区block
再跳到_Block_copy
,把断点放到最后,让_Block_copy
执行完,看值是否有变化
此时的值成了mallocBlcok
,地址也相应的发生了变化,主要是因为block地址
发生了改变,为堆block
调用流程
x11
从x9
加了了0x10
(换成十进制是16),然后blr x11
跳转执行, 从读取的x11
看是调用了_block_invoke
,这里也用了内存平移获取到block_layout
里的invoke
,invoke
前边是16
字节, 所以加了0x010
.
// Block 结构体
struct Block_layout {
//指向表明block类型的类
void *isa;//8字节
//用来作标识符的,类似于isa中的位域,按bit位表示一些block的附加信息
volatile int32_t flags; // contains ref count 4字节
//保留信息,可以理解预留位置,用于存储block内部变量信息
int32_t reserved;//4字节
//函数指针,指向具体的block实现的调用地址
BlockInvokeFunction invoke; // 8字节
//block的附加信息
struct Block_descriptor_1 *descriptor;
// imported variables
};
从x11
进来后,确实执行的函数。
前面提到的Block_layout
的结构体源码,从源码中可以看出,有个属性invoke
,即block的执行者,是从isa
的首地址平移 16
字节取到invoke
,然后进行调用执行的
签名
继续操作,读取x0
寄存器, 通过内存平移24
可以获取Block_descriptor_1
,其中的descriptor_3
有block的签名
继续通过flags
与上BLOCK_HAS_COPY_DISPOSE = (1 << 25)
判断是否有descriptor2
flags
与BLOCK_HAS_SIGNATURE = (1 << 30)
判断是否有descriptor3
,
继续打印签名:
签名部分
//无返回值
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
//encoding = (@),类型是 @?
type encoding (@) '@?'
//@是isObject ,?是isBlock,代表 isBlockObject
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
//所在偏移位置是8字节
memory {offset = 0, size = 8}
block的签名信息类似于方法的签名信息,主要是体现block的返回值,参数以及类型等信息
_Block_copy
分析
来到_Block_copy
源码:
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
// 重点提示: 这里是核心重点 block的拷贝操作: 栈Block -> 堆Block
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;//强转为Block_layout类型对象,防止对外界造成影响
if (aBlock->flags & BLOCK_NEEDS_FREE) {//是否需要释放
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {//如果是全局block,直接返回
return aBlock;
}
else {//为栈block 或者 堆block,由于堆区需要申请内存,所以只可能是栈区
// Its a stack block. Make a copy. 它是一个堆栈块block,拷贝。
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);//申请空间并接收
if (!result) return NULL;
//通过memmove内存拷贝,将 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;//可以直接调起invoke
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed 告知可释放
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;//设置block对象类型为堆区block
return result;
}
}
- 先容错处理,
arg
不存在,返回NULL
- 通过
flags
判断是否需要释放,需要直接释放 - 如果是
global block
,直接返回,需要copy - 剩下的俩种情况就是
栈block
和堆block
,因为堆区block
需要在内存中开辟内存空间,前边没有内存开辟代码,所以只能是栈block,注释也说了Its a stack block. Make a copy
- 申请内存空间,存放栈
blcok
- 通过
memmove
将block
拷贝到新开辟的内存上 - 设置
block
的isa
为_NSConcreteMallocBlock
- 申请内存空间,存放栈
_Block_object_assign
分析
分析_Block_object_assign
, 其中用的最多的是BLOCK_FIELD_IS_OBJECT
和BLOCK_FIELD_IS_BYREF
// Block 捕获的外界变量的种类
// Runtime support functions used by compiler when generating copy/dispose helpers
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
// see function implementation for a more complete description of these fields and combinations
//普通对象,即没有其他的引用类型
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
//block类型作为变量
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
//经过__block修饰的变量
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
//weak 弱引用变量
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
//返回的调用对象 - 处理block_byref内部对象内存会加的一个额外标记,配合flags一起使用
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
在_Block_object_assign
在底层编译代码中, 外部变量拷贝时调用的方法就是它
// Clang编译的cpp文件
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
在_Block_object_assign
中,
- 如果是
BLOCK_FIELD_IS_OBJECT
普通对象, 交给系统ARC
处理,拷贝对象指针,指向同一片内存空间, 内存引用计数+1,所以外边的相关引用变量不能释放
case BLOCK_FIELD_IS_OBJECT:
_Block_retain_object(object);
*dest = object;
break;
static void _Block_retain_object_default(const void *ptr __unused) { }//什么也没做
- 如果是
BLOCK_FIELD_IS_BLOCK
,block类型,则通过_Block_copy
操作,将block从栈区拷贝到堆区
,上边分析过
case BLOCK_FIELD_IS_BLOCK:
*dest = _Block_copy(object);
break;
- 如果是
__block修饰
,也就是BLOCK_FIELD_IS_BYREF
,会调用_Block_byref_copy
方法进行内存拷贝以及常规处理
case BLOCK_FIELD_IS_BYREF:
*dest = _Block_byref_copy(object); // 拷贝当前结构体的变量
break;
进入到_Block_byref_copy
方法
static struct Block_byref *_Block_byref_copy(const void *arg) {
//强转为Block_byref结构体类型,保存一份
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack 申请内存
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
//block内部持有的Block_byref 和 外界的Block_byref 所持有的对象是同一个,这也是为什么__block修饰的变量具有修改能力
//copy 和 scr 的地址指针达到了完美的同一份拷贝,目前只有持有能力
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
//如果有copy能力
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
//Block_byref_2是结构体,__block修饰的可能是对象,对象通过byref_keep保存,在合适的时机进行调用
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
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);
copy3->layout = src3->layout;
}
//等价于 __Block_byref_id_object_copy
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
- 将传入的对象强转为
Block_byref
类型,保存一份 - 按传入对象大小重新申请开辟了一块内存
- 如果已经拷贝过, 处理返回
src
和copy
的forwarding
指针是指向同一片内存,这就是__block
修饰能修改值的原因。
关键方法的执行顺序为:_Block_copy -> _Block_byref_copy -> _Block_object_assign
,正好对应上述的三层copy
三层copy总结
所以,综上所述,block的三层拷贝是指以下三层:
- 【第一层】通过
_Block_copy
实现对象的自身拷贝
,从栈区拷贝至堆区 - 【第二层】通过
_Block_byref_copy
方法,将对象拷贝为Block_byref
结构体类型 - 【第三层】调用
_Block_object_assign
方法,对__block
修饰的当前变量的拷贝
注:只有
__block修饰
的对象,block的copy才有三层
_Block_object_dispose
分析
同retain
和release
一样, block
有_Block_object_assign
,同样也有_Block_object_dispose
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents 当Blocks或Block_byrefs持有对象时,其销毁助手例程将调用此入口点以帮助处置内容
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF://__block修饰的变量,即bref类型的
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK://block类型的变量
_Block_release(object) ;
break;
case BLOCK_FIELD_IS_OBJECT://普通对象
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
- 进入
_Block_byref_release
源码,主要就是对象、变量的释放销毁
static void _Block_byref_release(const void *arg) {
//对象强转为Block_byref类型结构体
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
byref = byref->forwarding;//取消指针引用
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {//是否有拷贝辅助函数
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);//销毁拷贝对象
}
free(byref);//释放
}
}
}