聊聊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的内存管理那些事的主要内容,如果未能解决你的问题,请参考以下文章
动态内存管理那些事:malloccallocreallocfree