Block(四)对象类型的auto变量

我们在针对Block的剖析在进一步加深,了解了Block如何截获基本类型,了解Block的类型和copy操作,下面我们开始进入Block的内存管理世界

Block的内存管理,主要针对捕获外部对象类型的auto变量。

1
2
3
4
5
6
BFPerson *person = [[BFPerson alloc] init];
person.age = 28;
void (^block)(void) = ^{
NSLog(@"age %d", person.age);
};
block();

根据Block捕获auto基本变型的规律,针对对象,仍然适用

  • auto变量捕获后,Block中变量的类型和变量原类型一致;

  • static变量捕获后,Block对应的变量是对应变量的指针类型;

那么,auto对象与基本类型在Block内部有什么区别呢。

我们将从两方面讨论:

  1. Block是如何捕获对象类型的?
  2. Block内部是如何管理对象类型的?

一、捕获对象类型

以下代码位于Block捕获对象类型-Test.m中。

1. Block对象结构体

将下面代码重写:

1
2
3
4
5
6
7
8
9
- (void)testBlockCaptrueAutoObject
{
BFPerson *person = [[BFPerson alloc] init];
person.age = 28;
void (^block)(void) = ^{
NSLog(@"age %d", person.age);
};
block();
}

以上Block对象重写后的结构体如下:

1
2
3
4
5
6
struct __Test__test_block_impl_0 {
struct __block_impl impl;
struct __Test__test_block_desc_0* Desc;
BFPerson *person;
.....
};

可以看到:
Block对象仍然捕获auto变量后,保留了person对象的类型。

2.DescFunc

进一步观察Block对象中的Desc结构以及Func

1
2
3
4
5
6
7
8
9
10
11
12
13
//Func
static void __Test__test_block_func_0(struct __Test__test_block_impl_0 *__cself) {
BFPerson *person = __cself->person; // bound by copy
.....
}

//Desc
static struct __Test__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __Test__test_block_impl_0*, struct __Test__test_block_impl_0*);
void (*dispose)(struct __Test__test_block_impl_0*);
} __Test__test_block_desc_0_DATA = { 0, sizeof(struct __Test__test_block_impl_0), __Test__test_block_copy_0, __Test__test_block_dispose_0};

那么与基本类型的捕获区别在哪里呢?

我们观察与之前基本类型的区别,Func基本没有区别,Desc有区别,多了两个函数指针:

  • void (*copy)
  • void (*dispose)

那么继续查看这两个函数:

1
2
3
4
5
6
7
8
9
10
11
//copy函数
static void __Test__test_block_copy_0(struct __Test__test_block_impl_0*dst, struct __Test__test_block_impl_0*src)
{
_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

//dispose函数
static void __Test__test_block_dispose_0(struct __Test__test_block_impl_0*src)
{
_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

针对这两个函数,它们的作用就是:

函数 作用 调用时机
__Test__test_block_copy_0 调用 _Block_object_assign,相当于retain,将对象赋值在对象类型的结构体变量 __Test__test_block_impl_0中。 栈上的Block复制到堆时
__Test__test_block_dispose_0 调用 _Block_object_dispose,相当于release,释放赋值在对象类型的结构体变量中的对象。 堆上Block被废弃时

二、内存管理

在我们观察了Block对象捕获对象类型的内部结构之后,我们基本就能了解Block内部是如何管理的:

  • 在栈上的Block对象复制到堆上,对person进行retain
  • 在堆上Block被废弃时,对person进行废弃;

以上操作在ARC环境下,由系统帮助我们完成,但在MRC下,我们仍然要自己管理。

下面,我们就一步一步探索验证不同情境下的对象类型内存管理。

2.1 Block在栈上

我们将项目调成MRC环境,并在类BFPerson重写dealloc

1
2
3
4
5
6
7
@implementation BFPerson
- (void)dealloc
{
[super dealloc]; //MRC下打开,ARC下注释
NSLog(@"BFPerson delloc");
}
@end

测试如下代码:

image-20181209111540551

可以看到,在[person release]后,Block内部再次访问直接崩溃,说明Block内并没有对person对象进行强引用,使得person在内存中释放。

总结:

在栈空间,Block不会对auto变量进行强引用。

2.2 Block在堆上

2.1 Block强引用对象类型

我们将2.1,即上节中的代码,在ARC环境下再次试验,打印结果如下:

Block捕获对象类型[94670:4794884] begin

Block捕获对象类型[94670:4794884] class: __NSMallocBlock__

Block捕获对象类型[94670:4794884] age 28

Block捕获对象类型[94670:4794884] end

Block捕获对象类型[94670:4794884] BFPerson delloc

针对以上结果:

  1. ARC情况下,在Block赋值给__strong指针时,栈上的Block自动拷贝到堆;
  2. 拷贝的同时会将person对象进行retain操作,在这里相当于强引用;
  3. 调用block时,即使person出了大括号,系统会release一次,但由于block内部仍然强引用person,所以不会销毁;
  4. 在打印”end”后,block销毁,那么对person将进行一次release操作,所以person对象销毁。

2.2 Block弱引用对象类型

我们将block内部引用的auto变量改为__weak,我们知道__weak表示的是弱引用指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
BFBlock block;
NSLog(@"begin");
{
BFPerson *person = [[BFPerson alloc] init];
person.age = 28;
__weak BFPerson *weakSelf = person;
block = ^{
NSLog(@"age %d", weakSelf.age);
};
NSLog(@"class: %@", [block class]);
}
block();
NSLog(@"end");

上面代码输出的测试结果如下:

Block捕获对象类型[94923:4823185] begin

Block捕获对象类型[94923:4823185] class: __NSMallocBlock__

Block捕获对象类型[94923:4823185] BFPerson delloc

Block捕获对象类型[94923:4823185] age 0

Block捕获对象类型[94923:4823185] end

从输出结果我们很明显的看出,只要出了大括号,person对象就被销毁了,此后block调用,获取到的结果是0,这其实并不是person对象的age值。

我们将上面代码重写为C++代码,看看Block内部到底发生了什么?

1
$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

此时发生错误:

block代码转C++缺少运行时错误

针对上面问题,我们制定ARC下的运行时系统版本即可:

1
$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

得到的Block对象结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
BFPerson *__weak weakSelf; //weak变量
....
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
BFPerson *__weak weakSelf = __cself->weakSelf; // bound by copy
....
}

static void __main_block_copy_0(
struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
_Block_object_dispose((void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

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};

其实上面大体和强引用对象类似,只是其对应copydispose函数针对强引用和弱引用有所不同。

三、 总结

对象类型的auto变量

参考

示例代码

  1. Block捕获对象类型