前言
前段时间因为打算换工作,本来以为我已经复习好做好面试的准备了,结果约了一场面试,面试官问我了这个问题,我竟然回答得不知所措,所以赶紧学习脑补一下
关于block为什么要使用copy修复符,首先我们要了解一下关于栈区和堆区的概念
- 内存的栈区:由编译器自动分配和释放,存放函数的参数值,局部变量的值等,其操作类似数据结构的栈。
- 内存的堆区:一般由程序员分配和释放,若程序员不释放,程序结束时可能有OS进行回收,注意这里的内存堆区与数据结构中的堆是两回事,分配方式倒是类似于链表。
在iOS中block的类型
想必很多开发人员知道一般用copy修饰block,但是为什么要使用copy来修饰呢,其实在Objective-C语言中,一共有3中类型的block:
- _NSConcreteGlobalBlock 全局的静态 block,不会访问外部局部变量(显然包括无外部变量或者全局变量)
- _NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
- _NSConcreteMallocBlock 保存在堆中的 block,当应用计数为 0 时。
测试代码代码(摘自网络):
1 |
|
注意:测试代码需要把创建的类对象改成MRC才能打印到对应的数据 -fno-objc-arc
关于为什么block是copy来修饰,这涉及到block作用域的问题
首先,block是一个对象,所以block理论上是可以retain/release的,但是block在创建的时候它的内存默认是分配在栈(stack)上,而不是堆(heap)上的。所以它的作用域仅限创建时候的当前上下文(函数,方法,…),当你在该作用域外调用该block时,程序就会崩溃,所以为什么block需要使用copy来修饰,其实目的就是为了把它从内存的栈上挪动到内存的堆上,把其作用域扩大。
- 官方文档:

总结
- block内部没有调用外部局部变量时存放在全局区(ARC和MRC下均是)
- block使用了外部局部变量,这种情况下也是我们平时所常用的方式。MRC:block的内存地址显示在栈区,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序奔溃,在对block进行copy后,block存在堆区,所以在使用block属性时使用copy修饰,但是在ARC中的block都会在堆上,系统会默认对block进行copy操作
- 用copy,strong修饰block在ARC和MRC都是可以的,都是在堆区
补充
一个block要使用self,会处理成在外部声明一个weak变量指向self,然而为何会出现在block里又声明一个strong变量指向weaSelf?
原因:block会把写在block里的变量copy一份,如果直接在block里使用self,(self对变量默认是强引用)self对block持有,导致循环引用,所以这里需要声明一个弱引用weakSelf,让block应用weakSelf,打破循环应用。
而这样会导致另外一个问题,因为weakSelf是对self的弱应用,如果这个时候控制器pop或者其他方式导致引用计数变为0,就会被释放,如果这个block是异步调用而且调用的时候self已经释放了,这个时候weakSelf已经变成了nil.
当控制器(也可以是其他对象)pop回来之后(或者一些其他的原因导致释放),网络请求完成,如果这个时候需要控制器做出反映,需要strongSelf再对weakSelf强引用一下。
但是,你可能会有疑问,strongSelf对weakSelf强引用,weakSelf对self弱引用,最终也不是对self进行了强引用,会导致循环引用,答案是不会的,因为strongSelf是在block里面声明的一个指针,当block执行完毕后,strongSelf会释放,这时候讲不再强引用weakSelf,所以self会正确释放。
注意
- 一般情况下你不需要自行调用copy或者retain一个block,只有当你需要在block定义域以外的地方使用时才需要copy,copy将block从内存栈区移到堆区。
- 其实block使用copy是MRC留下来的也算是一个传统吧,在MRC下,如上述,在方法中的block创建在栈区,使用copy就能把他放到堆区,这样在作用域外调用block程序就不会奔溃。
- 在ARC下,使用copy与strong其实都一样,因为block的retain就是用copy来实现的,所以block使用copy还能装装逼,所以自己是从MRC下走过来的。
