block的类型详解
关于block的知识,在网络上的资料那是相当的多。不过这里还是想来谈谈自己对block的理解。本文不会深入到重写成C风格代码来理解,但也不会简单到仅仅是告诉你怎么用。
本文着眼点于从内存管理的角度来理解block。既然是从内存管理的角度来理解,那么我们就先来说一下Objc中block有哪几种类型吧。
在Objc中block被分为三种类型:
1. NSGlobalBlock——全局区,分布在数据段(.data)
2. NSStackBlock——分布在栈(stack)区域
3. NSMallocBlock——分布在堆(heap)区域
现在我们已经知道了block分为三种类型,不过需要注意的是在ARC情况下,其实已经基本不会有NSStackBlock这种block类型被分配了(这里说基本不会,至于为什么会这么说,详细会在关于属性修饰的部分谈)。纠其原因因该和内存自动管理的技术实现复杂度有关吧(个人猜测不做理论深入)。到这一步,我们接下来应该来,需要了解的就是这三种类型分别在什么样的情况下才会被分配。
总体来讲,我们需要遵循的原则就是:
1. 当block中引用了全局变量的时候,会被分配到NSGlobalBlock中
2. 当block没有引用任何block之外的变量的时候会分配到NSGlobalBlock中
对于上面这两点该怎么理解呢?这里强调一下,上述两点是并不是说同时满足。而是满足其中之一。也就是说,无论我们的block无论在什么地方被定义,只要block引用来全局变量或者block中没有引用block之外的变量就会被分配到NSGlobalBlock中。
这事个什么原则呢?当然,这个与生命周期有关了,对于全局变量的生命周期自不必说。对与没有引用block之外的变量怎么理解呢?这个当然还是与生命周期有关。我们知道在一个函数内定义的变量或者一个对象中的变量或属性其生命周期与函数或对象的生命周期是一致的,一旦他们被释放之后,如果block继续占用这些变量那么必将导致内存泄漏。而对与没有引用block之外的变量的block其外部变量的存在性并不影响block本身的运行,而只与block代码本身的存在性有关。
在了解了这些之后,我们还要来区分一下NSStackBlock与NSMallocBlock。NSStackBlock在ARC机制下是不存在的,这一点前面已经提到了,也就是说,在ARC机制下,所有除了分配成NSGlobalBlock之外的block都属于NSMallocBlock之列。
接下来,我们就来谈谈在MRC下的使用吧。在MRC下,除了上述情况之的block之外的block默认为NSStackBlock类型。这就决定了其生命周期与创建环境本身的生命周期相关。但是,在使用过程中,很多时候,其定义与使用并不在同一个地方,也就是说其创建环境的生命周期可能不能够保障其在调用的时候还继续延续着,那么我们该怎么办呢?对这个问题,我们要先要明确的是栈(stack)区域的内容属于自动分配的,由系统自动管理,既然如此,我们要想控制其生命周期的话,我们自然应该将分配管理权限从系统管理变为我们自己管理。而这个过程就需要我们将其copy到堆(heap)区域。这个过程,Objc提供了两个专门的函数Block_copy(...)和Block_release(...)。对于这两个方法的具体用法网上资料比较多就不细说了。
block作为属性传递
不过最后还有一点,就是block作为对象的属性传递问题。我们知道,对象的属性修饰符包括,weak,strong,retain,copy,assign。那么,在用这些修饰符时是怎样一个存在呢?其实,如果对block属性赋值,那么属性的block的类型与传入的block类型相同。
strong, retain, copy
strong,retain,copy三个属性修饰符,strong,retain属于计数+1操作。copy为复制操作。对用这三个修饰符修饰的属性赋值,最终block的类型为:存在外部参数引用的情况下分配为NSMallocBlock,不存在外部引用的情况下将分配为NSGlobalBlock。
weak,assign
对于weak,assign修饰符修饰block属性,weak目前仅在ARC条件下可用,而assign则均可用。不过,这里要注意的是,在直接对这两种类型传入一个定义好的block,和直接对block设置时把block定义本身作为值传入时,确是有所不同的。
这里举个例子吧,定义了一个TestBlock类。

接下来在使用的过程我们使用如下代码:

首先,我们可以看一下MRC下的的block类型分配如下图信息:

接着,对同样的代码在来观察一下ARC下的效果分配的block类型,如下:

到此,我们注意到,在MRC下和ARC下对内存分配类型的处理差别在于对block属性赋值时,MRC对block的内存分配策略采取的与前面讲到的MRC情况下的内存分配是一致的。差别在于,ARC下的分配策略,直接对多想属性为block的属性传入的数据为block定义,且block引用了外部变量(非全局变量)时,这个属性指向的block为NSStackBlock。这也就是为什么前面在谈Block类型时没有对ARC的类型为NSGlobalBlock和NSMallocBlock做绝对的定论。
那么,为什么会存在这样一个结局呢?个人根据经验理解(具体原因自己慢慢探索吧),这个原因就在与block的作用域以及修饰符的作用域本身上了。对于直接把block的定义赋值给block性质的属性是,block的作用域和定义它的函数本身并没太大的关系,它的上下文环境在某种程度上已经脱离了函数的上下问环境,但是由于属性本身亦是若引用,不对block做所在区域的处理。
网友评论