OC底层-Block本质(七、内存管理)

742 阅读6分钟

内存管理

前一篇文章中提到当block中捕获对象类型的变量时,block中的__main_block_desc_0结构体内部会自动添加copy和dispose函数对捕获的变量进行内存管理。

那么同样的当block内部捕获__block修饰的对象类型的变量时,__Block_byref_person_0结构体内部也会自动添加__Block_byref_id_object_copy和__Block_byref_id_object_dispose对被__block包装成结构体的对象进行内存管理。

当block内存在栈上时,并不会对__block变量产生内存管理。当blcok被copy到堆上时 会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(相当于retain)

我们可以通过下面的两张图看一下block复制到堆上时内存变化:

当block被copy到堆上时,block内部引用的__block变量也会被复制到堆上,并且持有变量,如果block复制到堆上的同时,__block变量已经存在堆上了,则不会复制。

当block从堆中移除时

• 会调用block内部的dispose函数

• dispose函数内部会调用_Block_object_dispose函数

• _Block_object_dispose函数会自动释放引用的__block变量(release)

block内部决定什么时候将变量复制到堆中,什么时候对变量做引用计数的操作。 __block修饰的变量在block结构体中一直都是强引用,而其他类型的是由传入的对象指针类型决定。

将上述代码转化为c++代码查看不同变量之间的区别

源码

上述__main_block_impl_0结构体中看出,没有使用__block修饰的变量(object 和 weadObj)则根据他们本身被block捕获的指针类型对他们进行强引用或弱引用,而一旦使用__block修饰的变量,__main_block_impl_0结构体内一律使用强指针引用生成的结构体。

__block修饰对象类型的变量生成的结构体内部多了__Block_byref_id_object_copy和__Block_byref_id_object_dispose两个函数,用来对对象类型的变量进行内存管理的操作。而结构体对对象的引用类型,则取决于block捕获的对象类型的变量。weakPerson是弱指针,所以__Block_byref_weakPerson_2对weakPerson就是弱引用,person是强指针,所以__Block_byref_person_1对person就是强引用。

__main_block_copy_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*/);
    _Block_object_assign((void*)&dst->object, (void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_assign((void*)&dst->weakObj, (void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);
}

__main_block_copy_0函数中会根据变量是强弱指针及有没有被__block修饰做出不同的处理,强指针在block内部产生强引用,弱指针在block内部产生弱引用。被__block修饰的变量最后的参数传入的是8,没有被__block修饰的变量最后的参数传入的是3。

__main_block_dispose_0函数

当block从堆中移除时通过dispose函数来释放他们。

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_dispose((void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);
    
}

__forwarding指针

__forwarding指针指向的是结构体自己。当使用变量的时候,通过结构体找到__forwarding指针,在通过__forwarding指针找到相应的变量。这样设计的目的是为了方便内存管理。通过上面对__block变量的内存管理分析我们知道,block被复制到堆上时,会将block中引用的变量也复制到堆中。

我们重回到源码中。当在block中修改__block修饰的变量时。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref
            (age->__forwarding->age) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_jm_dztwxsdn7bvbz__xj2vlp8980000gn_T_main_b05610_mi_0,(age->__forwarding->age));
        }

通过源码可以知道,当修改__block修饰的变量时,是根据变量生成的结构体这里是__Block_byref_age_0找到其中__forwarding指针,__forwarding指针指向的是结构体自己因此可以找到age变量进行修改。

当block在栈中时,__Block_byref_age_0结构体内的__forwarding指针指向结构体自己。

而当block被复制到堆中时,栈中的__Block_byref_age_0结构体也会被复制到堆中一份,而此时栈中的__Block_byref_age_0结构体中的__forwarding指针指向的就是堆中的__Block_byref_age_0结构体,堆中__Block_byref_age_0结构体内的__forwarding指针依然指向自己。

此时当对age进行修改时

// 栈中的age
__Block_byref_age_0 *age = __cself->age; // bound by ref
// age->__forwarding获取堆中的age结构体
// age->__forwarding->age 修改堆中age结构体的age变量
(age->__forwarding->age) = 20;

通过__forwarding指针巧妙的将修改的变量赋值在堆中的__Block_byref_age_0中。

因此block内部拿到的变量实际就是在堆上的。当block进行copy被复制到堆上时,_Block_object_assign函数内做的这一系列操作。

被__block修饰的对象类型的内存管理

源码

// __Block_byref_person_0结构体声明

__attribute__((__blocks__(byref))) __Block_byref_person_0 person = {
    (void*)0,
    (__Block_byref_person_0 *)&person,
    33554432,
    sizeof(__Block_byref_person_0),
    __Block_byref_id_object_copy_131,
    __Block_byref_id_object_dispose_131,
    
    ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"))
};

之前提到过__block修饰的对象类型生成的结构体中新增加了两个函数void (__Block_byref_id_object_copy)(void, void*);和void (__Block_byref_id_object_dispose)(void);。这两个函数为__block修饰的对象提供了内存管理的操作。

可以看出为void (__Block_byref_id_object_copy)(void, void*);和void (__Block_byref_id_object_dispose)(void);赋值的分别为__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131。

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

上述源码中可以发现__Block_byref_id_object_copy_131函数中同样调用了_Block_object_assign函数,而_Block_object_assign函数内部拿到dst指针即block对象自己的地址值加上40个字节。并且_Block_object_assign最后传入的参数是131,同block直接对对象进行内存管理传入的参数3,8都不同。可以猜想_Block_object_assign内部根据传入的参数不同进行不同的操作的。

通过对上面__Block_byref_person_0结构体占用空间计算发现__Block_byref_person_0结构体占用的空间为48个字节。而加40恰好指向的就为person指针。

也就是说copy函数会将person地址传入_Block_object_assign函数,_Block_object_assign中对Person对象进行强引用或者弱引用。

__weak修饰变量

源码

__main_block_impl_0中没有任何变化,__main_block_impl_0对weakPerson依然是强引用,但是__Block_byref_weakPerson_0中对weakPerson变为了__weak指针。

也就是说无论如何block内部中对__block修饰变量生成的结构体都是强引用,结构体内部对外部变量的引用取决于传入block内部的变量是强引用还是弱引用。

结论

对象类型的auto变量、__block变量

  • 当block在栈上时,对它们都不会产生强引用

  • 当block拷⻉到堆上时,都会通过copy函数来处理它们 __block变量(假设变量名叫做a) _Block_object_assign((void*)&dst->a, (void*)src->a, 8/BLOCK_FIELD_IS_BYREF/);

  • 对象类型的auto变量(假设变量名叫做p) _Block_object_assign((void*)&dst->p, (void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/); 当block从堆上移除时,都会通过dispose函数来释放它们

  • __block变量(假设变量名叫做a) _Block_object_dispose((void*)src->a, 8/BLOCK_FIELD_IS_BYREF/);

  • 对象类型的auto变量(假设变量名叫做p) _Block_object_dispose((void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/);

被__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)