Kealdish's Studio.

抽丝剥茧之block(上)

字数统计: 1.7k阅读时长: 5 min
2016/03/12 Share

block在iOS下的应用场景非常广泛,相比于delegate严谨繁琐的语法,block则更加灵活。因而,深入理解block还是很有必要的,以免在开发过程中踩到坑。整个关于block的系列着重点不在于介绍block的定义和使用,而是着眼于block中的关键概念以及难以理解的地方,系列索引在这里:

抽丝剥茧之block(上)

抽丝剥茧之block(下)

问题和结论

本篇主要的探讨的问题是声明block变量时的修饰符使用strong还是copy?两者是否有区别?

这里先给出结论:在引入ARC之后,在ARC下使用strongcopy对于block而言,不管是实例变量、局部变量还是全局变量都是没有问题的,也可以说这两者几乎没有区别。

栈和堆

先说是不是,再说为什么,要解释清楚这个问题要从堆和栈讲起。iOS系统中运行代码使用的空间在三个不同的内存区域,分成三个段:“text segment “,“stack segment ”,“heap segment ”。

栈:
在现代操作系统中,一个线程会分配一个stack. 当一个函数被调用,一个stack frame(栈帧)就会被压到stack里。里面包含这个函数涉及的参数,局部变量,返回地址等相关信息。当函数返回后,这个栈帧就会被销毁。而这一切都是自动的,由系统帮我们进行分配与销毁。

堆:
提供一个保存中介贯穿函数的执行过程,全局和静态变量保存在“heap”中,直到应用退出。堆,内存可以随时在堆中分配和销毁。我们需要明确请求内存分配与内存销毁。

stack 对象的优点主要有两点,一是创建速度快,二是管理简单,它有严格的生命周期。stack 对象的缺点是它不灵活。创建时长度是多大就一直是多大,创建时是哪个函数创建的,它的owner 就一直是它。不像heap 对象那样有多个owner ,其实多个owner 等同于引用计数。只有heap 对象才是采用“引用计数”方法管理它。在栈中创建对象,只要栈的剩余空间大于stack 对象申请创建的空间,操作系统就会为程序提供这段内存空间,否则将报异常提示栈溢出。而在堆中创建对象,操作系统对于内存heap 段是采用链表进行管理的。操作系统有一个记录空闲内存地址的链表,当收到程序的申请时,会遍历链表,寻找第一个空间大于所申请的heap 节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。

我们应该知道OC中的对象都分配在堆而不是栈中,其原因是stack对象的生命周期所导致的问题。例如一旦函数返回,则所在的stack frame就会被摧毁。那么此时返回的对象也会一并摧毁。这个时候我们去retain这个对象是无效的。因为整个stack frame都已经被摧毁了。简单而言,就是stack对象的生命周期不适合Objective-C的引用计数内存管理方法。同时,2. stack对象不够灵活,不具备足够的扩展性。创建时长度已经是固定的,而stack对象的拥有者也就是所在的stack frame。

block存储

由上文我们了解到iOS中的对象存储在堆上,那block存储在堆还是栈上呢?一般而言,block存储在栈上,这是编译器内部的优化策略,并不会直接对编写的代码产生影响。这样做的好处在于当block创建出来并作为方法的参数传递,方法调用后将其抛弃时,block可以快速的在栈上进行内存分配并且销毁的时候不需要涉及到堆(动态内存池)。

将对象与局部变量做下比较。局部变量在栈中创建出来,当变量所属的方法返回时会自动销毁该变量,并且可以被方法调用者以地址方式传递给被调用方法。当变量的调用方法返回后临时变量的地址不会被存储和使用了,此时该变量已不再存在。

然而,在必要情况下,对象则期望比它们的创建方法生命周期更长,因此,与局部变量不同,对象是在堆上进行分配并且不会被创建方法返回时自动销毁,是否销毁取决于对象是否还被“需要”,“需要”则是由ARC自动管理。

再回到block,在栈上创建block在性能方面有好处但同时也会引起一个问题:如果block的生命周期需要超过它的创建方法,就像对象一样,那么它需要在创建方法的栈销毁之前移动到堆上。
在block第一次release时,由于编译器那时候无法自动处理需要移动到堆上的block的生命周期,所以需要开发者手动调用block_copy()方法将block复制到堆上。但是,在OC上层语言层面调用底层方法手动管理编译器中的变量生命周期显示时不合适的(block是
C语言结构)。因而,之后苹果发布了新版本的编译器来改善这方面的问题,开发者可以调用
[block copy]代替block_copy(block)。然后,编译器会自动将block从栈复制到堆上,不过,这点并没有在官方文档中提及。

关于block的修饰符说明,在14年的苹果文档WorkingwithBlocks中提到block类型的属性应使用copy,但是不多久就被删除了,原文在这里:

You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior.

内容的大致意思是使用copy修饰block在规范上更推荐,它预示了block会发生的结果行为。实际上,在ARC下不需要我们去关心block的拷贝,ARC会自动完成,因而strongcopy并没有本质上的区别,只是在规范上更倾向于copy而已。

CATALOG
  1. 1. 问题和结论
  2. 2. 栈和堆
  3. 3. block存储