一顿饭的功夫带你了解block是如何内存管理的

请添加图片描述

✅作者简介:大家好我是瓜子三百克,一个非科班出身的技术程序员,还是喜欢在学习和开发中记录笔记的小白博主!
📃个人主页:瓜子三百克的主页
🔥系列专栏:OC语法


1、这里先思考一个问题,为什么是block内部自行内存管理呢?

根据谁创建谁销毁的原则,因为对象在block结构体中,由结构体持有,那么block就要自行管理什么时候持有对象,什么时候销毁对象。

2、这里还需要思考一个问题,什么情况下需要管理内存,什么情况下不需要管理内存。

2.1、不需要内存管理的情况:

1、当block在栈上:block内部不会对__block变量产生强引用,所以不需要内存管理
2、基本数据类型,没有加 __block 修饰符的:block内部是值存储,不涉及对象管理,所以不需要内存管理。
3、static变量和全局变量:内存放在数据段,由程序统一管理,可全局访问,长期持有而且不会销毁,所以不需要内存管理。

除了以上几个情况,那么只剩下堆区的数据,需要进行内存管理了。

2.2、需要进行内存管理的情况:

1、对象类型的auto变量。
2、引用了 __block 修饰符的变量。

1、底层结构分析内存管理

那么block内部又是怎么进行内存管理的呢?下面开始一一揭晓。

一言不合先上代码,分析一下,加和不加 __block 修饰符,底层结构的异同点:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    	// 对象类型的 auto 变量
        NSObject *obj = [[NSObject alloc] init];
        // 加了 __block 修饰符的基本数据类型的变量
        __block int age = 10;
        void(^block)(void) = ^{
            NSLog(@"Hello:%@",obj);
            NSLog(@"Hello:%d",age);
        };
        block();
    }
    return 0;
}

底层代码结构:

// 1、程序入口
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        
        NSObject *obj = objc_msgSend(objc_msgSend)(objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        
        // 2、基本数据类型age变量,这里被封装成age结构体对象类型
        __Block_byref_age_0 age = {0,&age, 0, sizeof(__Block_byref_age_0), 10};
        
        void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, obj, &age, 570425344));
        
        block->FuncPtr(block);
    }
    return 0;
}


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;//3、这里添加了auto变的内存管理操作
  NSObject *obj;// 4、与main中的对象类型一致
  __Block_byref_age_0 *age; // 5、__block 修饰之后,这里封装好了的结构体对象
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, __Block_byref_age_0 *_age, int flags=0) : obj(_obj), age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


// 2.1、当block从栈copy到堆上的时候,会执行这个copy函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
		// 如果是没有__block修饰符的对象类型:其引用类型同外部传进来的保持一致
    _Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
    // 添加了 __block修饰符的变量,一定是强引用关系
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
}

// 2.2、当 block 销毁的时候,会调用这个 dispose 函数,把block结构体内部持有的变量一起销毁。
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    
    _Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    
}

// 5、__block 修饰之后,这里封装好了的结构体
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

根据底层结构,用更形象的图进一步分析:

2、当block被copy到堆时

----> 会调用block内部的copy函数
--------> copy函数内部会调用_Block_object_assign函数
------------> _Block_object_assign函数会对__block变量形成强引用(retain)

在这里插入图片描述

在这里插入图片描述

3、当block从堆中移除时

----> 会调用block内部的dispose函数
--------> dispose函数内部会调用_Block_object_dispose函数
------------> _Block_object_dispose函数会自动释放引用的__block变量(release)

在这里插入图片描述
在这里插入图片描述

4、对象类型的auto变量 VS 添加__block修饰符的变量

相同点:
1、当block在栈上时,对它们都不会产生强引用

2、当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*/);

3、当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*/);

不同点:
1、添加__block修饰符的变量,在block结构体中都是强引用。
2、对象类型的auto变量,根据所指向对象的修饰符(__strong__weak__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用 (注意:这里仅限于ARC时会retain,MRC时不会retain)
在这里插入图片描述

5、__block的__forwarding指针

从前面的示例代码我们发现,__block修饰的age变量,被封装成结构体类型 __Block_byref_age_0

struct __Block_byref_age_0 {
  void *__isa;// isa地址指针
__Block_byref_age_0 *__forwarding;// 指向自己的指针
 int __flags;
 int __size;
 int age;//变量
};

这里有一个很奇怪的情况,为什么还要有一个 __forwarding 指向自己的指针呢?
其实这里涉及到了__block变量从栈上被copy到堆上情形,具体逻辑见下图:
在这里插入图片描述1、当__block变量在栈上时, __forwarding 指向是自己本身的指针,可以取到值。
2、当__block变量在堆上时,__forwarding 指向也是自己本身的指针,可以取到值。
3、当__block变量从栈上复制到堆上时, _Block_object_assign 函数会对__block变量形成强引用(retain),此时栈上的 __forwarding 指向复制到堆上的 __block 变量的结构体指针,没毛病。


**🏆结束语🏆 **

最后如果觉得我写的文章对您有帮助的话,欢迎点赞✌,收藏✌,加关注✌哦,谢谢谢谢!!

在这里插入图片描述

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 31
    评论
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瓜子三百克

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值