iOS中的Reference Counting详解
0x00 问题的引入
- 前一阵子表哥给了我一道知乎的iOS开发岗位面试题,听说还是那种类似于“一票否决”的题目,考察应试者的编程能力。我仔细一看是关于MRC的一道题,也就是在考察Reference Counting。(代码为了方便运行测试,略有改动,但是核心思路无变化)
// 应用背景:MRC模式 // 请说出所有NSLog的输出值,并解释理由。 #import <Foundation/Foundation.h> @interface Zhihu : NSObject + (int) setKanShanToZhihu; @end @implementation Zhihu + (int) setKanShanToZhihu { NSMutableArray *zhihu=[[[NSMutableArray alloc]init]retain]; NSObject *kanshan=[[NSObject alloc]init]; [kanshan retain]; [zhihu addObject:kanshan]; NSLog(@"%d",(int)[kanshan retainCount]); [kanshan retain]; [kanshan release]; [kanshan release]; NSLog(@"%d",(int)[kanshan retainCount]); [zhihu removeAllObjects]; NSLog(@"%d",(int)[kanshan retainCount]); [kanshan release]; return (int)[kanshan retainCount]+(int)[zhihu retainCount]; } @end int main(int argc, const char * argv[]) { NSLog(@"%d",[Zhihu setKanShanToZhihu]); return 0; }
- 大家可以去编译一下这个题目,新建一个Xcode工程,在Compile Source中加入
-fno-objc-arc
,关闭ARC,运行结果是3 2 1 3
。
0x01 MRC和ARC
- 最早的时候Objective-C和C++一样,也是手动管理内存的。不过OC使用的是Reference Counting(引用计数)的方式,也就是说,同一个内存空间,引用计数显示了目前有多少个指针正在指向这个内存空间。显然,当引用计数等于0的时候,这块内存就不再有用了,系统就会将其空间释放。这里面OC就提供了一些方法,允许程序员管理引用计数。
//对该对象的引用计数+1,返回一个新的指针指向该内存 - (instancetype) retain; //对该对象的引用计数-1 - (oneway void) release; //输出该对象的引用计数数值 - (NSUInteger) retainCount;
- 大家也看出来了,这样管理也很麻烦,程序员需要关注大量的指针问题,还有可能出现强引用循环的问题。所以从iOS 5.0开始,苹果引入了ARC机制,ARC即自动引用计数(Automatic Reference Counting),这才把iOS开发者们从引用计数中解放出来,而原来的方式就称为MRC了,即手动引用计数(Manual Reference Counting)。
- 既然iOS 5之后就可以用ARC了,现在App Store的最低支持版本已经是iOS 8.0了,为什么知乎的面试题还要考MRC呢?显然是为了考察iOS面试者对于引用计数机制的了解啦。
0x02 题目解答
- 知乎这个面试题确实很考验iOS面试者的编程功底,将手动引用计数的管理应用到了极致。
- 首先第1句创建了一个可变长度的数组,变量名字为zhihu
NSMutableArray *zhihu=[[[NSMutableArray alloc]init]retain];
- 这里我们发送
alloc
消息,就会分配一块内存,再发送init
消息进行初始化,这样本身就会返回一个指针,引用计数变为1。但是偏偏又调用了一次retain
,这时候引用计数又会加1,变为2。所以这一个语句使得引用计数+2。 - 第2句和第3句正常创建了一个对象kanshan,引用计数为1,随后又进行了
retain
,引用计数为2。
NSObject *kanshan=[[NSObject alloc]init]; [kanshan retain];
- 第4句通过向zhihu数组发送
addObject
消息,将kanshan对象加入数组。
[zhihu addObject:kanshan];
- 这里因为向数组发送了
addObject
消息,数组中就也会保存一个指针指向这片内存,kanshan的引用计数再次加1。所以第一次输出的结果为3
。 - 然后连续经过三句话,引用计数先加1后减2,结果当然是减1。
[kanshan retain]; [kanshan release]; [kanshan release];
- 所以第二次输出的结果为
2
。 - 随后又向zhihu发送了
removeAllObjects
消息,清空了整个数组
[zhihu removeAllObjects];
- 这时候kanshan的引用计数也会受到影响,因为它不再保存在数组中,所以引用计数减1,第三次输出的结果是
1
。 - 最后又调用了一次
release
[kanshan release]; return (int)[kanshan retainCount]+(int)[zhihu retainCount];
- 按理来说kanshan的引用计数应该降为0了,被释放。但是在发送
retainCount
消息的时候,为了避免对已经释放的内存发送消息,系统会自动持有一个指向该块内存的指针。而zhihu的引用计数还是为2,所以,最后返回值的总计数值是3
。
0x03 API文档中对retainCount消息的说明
- 在API文档中,苹果直戳了当的表示:
- 让我们不要使用这个方法,苹果的理由是这样表述的:
- 这个函数对调试内存管理问题没有用处,因为所有的在framework中的对象都会保留了一个对象以便于持有对这个对象的引用。同时在autorelease pool中对象也可能被延迟释放。所以说我们不可能从这里获取有关内存管理的有用信息。
- 所以,即使我们把kanshan的引用计数降为0,系统仍然会保留一个指针指向kanshan,以持有一个对kanshan的引用,这样我们在调用retainCount的时候才不会出现错误。
0x04 总结
- 实际上苹果费尽心思让我们不要使用MRC,不要去考虑引用计数的问题。为了解决强引用循环的问题,苹果甚至设计了weak指针。在最新的Swift语言中,也必须进行一些unsafe的生命才允许你手动管理内存。但是作为一名合格的iOS开发人员,个人认为还是有必要了解一些关于引用计数的知识的,这也是知乎出这道题的意义。
相关推荐
leitingdulante 2020-11-03
huangkun 2020-10-22
leitingdulante 2020-10-21
硬币0 2020-10-15
moses 2020-09-22
ZuoYanDeHuangHun 2020-09-18
chsoft 2020-09-17
fanxiaoxuan 2020-09-17
惠秀宝 2020-09-08
zhousanzhou 2020-08-26
MatrixHero 2020-08-20
xjp 2020-08-17
定格 2020-08-15
Mryiyi 2020-08-07
好好学习天天 2020-07-28
好好学习天天 2020-07-21
Mryiyi 2020-07-08
RocketJ 2020-07-03