洋气的 autorelease
0、简单的说一句
autorelease 已经在 iOS 界叱咤风云这么多年,现在网上也有很多类似的文章,今天也来造个轮子。关于 autorelease 往往会出现这三个问题:
- 1、是做什么的,有什么用
- 2、__autoreleasing 的使用
- 3、autorelease 后的对象,是在什么时候被销毁的
当然,很多的时候提问者是直接问第3个问题,只要第3问回答正确了,都没有问题了。如果是你,你会怎么回答。关于这个问题,我会先给出常规的错误答案,然后逐一解释。
建议:本文主要是看过程、不要追求最终的答案。想看最后答案,直接看第三部分。
一、错误回答
你是否会这样的回答?!当运行跳出大括号之后,会给当前自动释放池中发送过 autorelease 消息的对象都发送一条 release 消息。貌似很多的小伙伴都会这么回答,遗憾的是 这个回答是错误的,接下来会给出错误的理由。十分的题、也就对了1分,在职场上回答不到8分的回答都是0分。
为什么是错误的回答呢?因为与 大括号 没有 多大 的关系。具体解释,请看下文。
在开始解释之前,先定义一个Class,命名为:HGObject,定义如下:
#import <Foundation/Foundation.h>
@interface HGObject : NSObject
/** 名称 */
@property (nonatomic, copy) NSString* name;
@end
====调皮的分割线====
#import "HGObject.h"
@implementation HGObject
- (void)dealloc {
NSLog(@" %@ 被释放", _name);
[super dealloc];
}
@end
总的来说,就是定义了一个属性 name,然后重写了一下 -dealloc 方法。从上面的带来来看,我们接下来的演示中是在 MRC 环境下进行的。
二、与大括号的关系
2.1 不会被释放的情况
不会被释放你看了之后,你会说:这肯定不会被释放的,因为没有再释放池中。 对、很有道理,确实也是这样,autorelease 必须要在池子中才会有效。那么我就再迁移到池子中,看一下效果。
结果还是一样的:不会被释放。看到这里的你是不是很激动,不信你就试试,如果释放了记得告诉我。关于这种情况,也有这么解释的:因为这里的 autoreleasepool 没有被释放,所以不会被释放,这种解释 100% 是错误的解释。但是如果上面的两个地方将 autorelease 换成 release ,那么都是可以释放的。
到这里,是不是就说明了与大括号无关了?!
来简单的分析一下,在上面的两种使用 autorelease 之后不会被释放的情况。在上面的两个地方,是一个非常不应该的特例,因为我们一般不会在这个地方写代码。但是有一点很情况的是:在上面的两个地方都没有被进入 UIApplicationMain,在 UIApplicationMain 有一个特别重要的机制,叫:运行循环,美其名曰 NSRunLoop。如果说能想到这一点,那么就算是入门了。
看到上面的介绍,你会不会又会很激动的说:对啊,我说的就是在运行循环中的与大括号有关的,在大括号结束之后就释放了。 还想说的是,这也是错误的。是与大括号有关系,但是并不是 那 一层的关系。
2.2 与大括号的那一层关系
先看一下这个代码:
image.png
关于上面的代码,当我点击屏幕的时候,会打印
两个 log 日志信息, 是先打印 testAutorelease 执行完毕 呢,还是先打印 obj 对象 被释放。思考一下、把你的答案放在心中,稍后公布答案。
、
。
,
、
;
‘
【
】
0
9
8
7
6
5
4
3
2
1
正确的顺序
看到这个结果,不知道是否有小伙伴开始怀疑人生。关于这个问题,我们先来打两个断点:
两个特别的断点
其实我们通过上面的结论,当断点跳过 32 行的断点之后,obj 是没有被销毁的,那么运行到 26 行的时候是否被销毁了呢?我们试一下:
对、运行到 26 行依然没有被销毁,那么问题来了:到底是在什么时候销毁的呢?你可能会说:这不废话么?那肯定是 26 行之后就销毁了。现象是没有错的,的确是在 26 行之后就销毁了。但是在揭穿真面目之前,还想再做一个实验,将代码换成这样的:
image.png通过上面的介绍,这里的打印 log 的顺序是什么呢?这个得要很认真的思考了。
、
。
,
、
;
‘
【
】
0
9
8
7
6
5
4
3
2
1
其实转了一圈,恐怕都晕了吧。至此 autorelease 与大括号的关系,大家都有一个明确的理解了。以后别人问你的话,你还会怎么回答呢?
三、最后一公里
在揭穿之前,希望大家静一静,我们先来介绍一个小指令,关于 lldb 的。很多时候我们总想看一个事件的调用栈,我们可以使用这样的代码:
// 单元调用栈
NSLog(@"%@", [NSThread callStackSymbols]);
也可以直接在 lldb 中这样使用:
以上都是可以的,但是还有一个更简单的, 直接在 lldb 处输入 bt, 然后回车即可。
我为什么要介绍这个呢?是因为很多的时候 Xcode 的这里是显示不全的:
现在的 Xocde 很多有用的都被省略了
现在的 Xocde 很多有用的都被省略了,想看只能通过命令了。赚了一圈,也大了不少的断点,就是没有在 HGObject 中的 -dealloc 中打过断点,要想知道 autorelease 的对象是在什么时候被释放的,直接在这个地方打个断点看看不就可以了么?是啊、我的错,把这里给忘记了。[勇于承认错误,是我一直以来的光荣传统美德]。
那么我们就上面的那个状态,执行以下 bt 指令,信息如下:
这张图片的信息,粗略的介绍一下。通过这张图片能看到一个陌生既熟悉的关键字 AutoreleasePoolPage,这又是什么?借用在一个微信群中某大神的一个解释,他的原话:autoreleasepool不是一个大栈,是分一个一个固定大小的page,双向链表连起来的。这里面所指的 page 应该就是这个 AutoreleasePoolPage。具体这个 pop 是在什么时候被调用的,这与 NSRunloop 有关。
四、推荐
推荐一下我的其它文章:
- 1、定时器集合:介绍了所有定时器的用法以及注意事项。
- 2、简单易用 的 iOS 分类 : 主要是文本编辑与正则表达式的封装。
- 3、神气的 iOS 打包 :不知道为什么这篇文章的点击率很高,刚刚还有小伙伴在点赞。可能是因为标题取得有点霸气。
- 4、iOS单例的精心设计历程 : 开发中应该不会这么去设计一个单例,但是这里面介绍了很多的细节,值得学习参考。