聊聊Block的内存管理那些事

Posted shenyingqiang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了聊聊Block的内存管理那些事相关的知识,希望对你有一定的参考价值。

对于大多数ios开发人员来说,Block应该不算陌生,iOS4.0系统已开始支持Block,在编程过程中,Block被Objective-C看成是对象,它封装了一段代码,这段代码可以在任何时候执行。


Block可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。它是对C语言的扩展,用来实现匿名函数的特性。

       

Block的使用很像函数指针,不过与函数最大的不同是:Block可以访问函数以外、词法作用域以内的外部变量的值。


换句话说,Block不仅 实现函数的功能,还能携带函数的执行环境,更通俗的讲Block就是能够读取其它函数内部变量的函数。


Block的特殊特性方便了开发人员的使用,但是Block的内存问题却是最坑的地方,Block的内存需要开发人员自己管理,错误的内存管理会造成循环引用内存泄露,或者内存因为提前释放造成崩溃。


因此,Block的内存管理是很重要的,本文将主要讨论Block使用过程中的内存管理问题。

       

首先要明白,Block其实包含两部分,一部分是Block所执行的代码,这一部分在编译的时候已经生产好了。


另一部分是Block执行时所需要的外部变量值的数据结构,注意的是Block会将使用到的变量的值拷贝到栈上。


再细分Block的类型的话,根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock。


下面介绍下这三种类型的Block:


1.NSGlobalBlock,类似函数,存储在程序的数据区域(text段),我们只要实现一个没有对周围变量没有引用的Block,就会显示为是它,对于Global的Block,我们无需多处理,不需retain和copy,即使copy,也不会我们以为的copy到堆区,内存不会发生变化!操作都无效,例如:

技术分享

 

2.NSStackBlock:位于栈内存,函数返回后Block将无效;对于定义的Block加入了对定义环境变量的引用,也就是说内部使用了外部变量,就是NSStackBlock。


对于Stack的Block,如果不做任何操作,随栈自生自灭。


而如果想让它获得比stack更久的生命,那就调用Block_copy(),或者copy修饰,让它搬家到堆内存上,这也是我们为什么一直用copy修饰Block的原因。


对于NSStackBlock栈区Block,我们分MAR和ARC两种情况讨论,因为两种模式下的内存机制是不同的。


在MRC模式下,分析如下示例打印结果:

技术分享

 

结果分析:

当Block中使用了外部变量,Block为NSStackBlock类型,存储在栈区,当函数执行结束后


改Block就会被释放,调用copy后,栈区Block被copy到了堆区NSMallocBlock(下面会介绍)。


在ARC模式下,分析以上相同示例打印结果:

技术分享

分析结果:在ARC模式下,没有了__NSStackBlock__类型,不要认为ARC没有了栈区Block这种类型。


其实在ARC下,生产的Block默认也是NSStackBlock类型,只是在变量赋值的时候,系统默认对其就行了copy。


从NSStackBlock给copy到堆区的NSMallocBlock类型,而在非arc中,则需要手动copy.。


3.NSMallocBlock:

只需要对NSStackBlock进行copy操作就可以获取,所以,当我们定义的Block要在外部回调使用的时候,在MRC下,我们需要copy的堆区,永远的持有,不让释放;


在堆区的NSMallocBlock,我们可以对其retain,release,copy(等价于retain,引用计数的加一)。


在ARC下,系统会给我们copy到堆区,不需我们管理了,所以个人还是喜欢ARC的模式,避免了很多不必要的麻烦。


Block对外部变量的存取管理


1、局部变量

当在Block中使用局部变量时,在Block中只读。


Block会copy改局部变量的值,在Block中作为常量使用,是不允许修改的,只能使用,所以即使变量的值在Block外改变,也不影响他在Block中的值。


示例分析如下:

技术分享

2、static修饰符的全局变量,或者全局属性

因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量.


示例分析:

技术分享

 

3、__block修饰的变量

Block变量,被__block修饰的变量称作Block变量。 


基本类型的Block变量等效于全局变量、或静态变量,也就是上面例子值会被共同修改,因此,Block中如果使用局部变量,如果想修改这个变量的值,需要__Block 修饰变量,才能使用。


4、循环引用retain cycle

因为Block在拷贝到堆上的时候,会retain其引用的外部变量,那么如果Block中如果引用了他的宿主对象,(也就是定义Block的类)那很有可能引起循环引用,既在自身类的Block中用了self对象,例如:

技术分享

 

分析:

因为Block是当前self属性,self拥有了Block,在ARC下为强引用;


当在Block中使用了self的时候,Block便强引用了self,两者相互持有,无法释放。


解决方法是ARC 下__weak修饰self:__weak Class *weakSelf =self; MRC下__weak改为__block.


补充:对于两个对象之间的Block回调,只有双方持有的时候才会造成循环引用,例如:


ClassA 中定义了Block闭包函数,ClassB中的Block指针去回调ClassA的Block,


此时如果Block中使用了ClassA的self自身对象,ClassB便强引用了ClassA,此时不会造成循环引用。


只有ClassB为ClassA的属性的属性,既ClassA也持有ClassB才会造成循环引用问题。

























































以上是关于聊聊Block的内存管理那些事的主要内容,如果未能解决你的问题,请参考以下文章

block没那么难:block和变量的内存管理

动态内存管理那些事:malloccallocreallocfree

动态内存管理那些事:malloccallocreallocfree

JVM内存的那些事

JVM内存的那些事,你了解多少?

聊聊测试管理的那些事之管事篇