让人头疼的“引用计数”

想必每一个初学者在接触到iOS时对于他们内存管理方式肯定有些不太习惯,即使是如今ARC已经流行开来的情况下,为了更懂内存,我们还是要和这个讨厌的家伙——“引用计数”打打交道。
  对于已经接触过引用计数的朋友可以先看看以下代码:

“灵异事件”

//class1 仅仅是一个自定义类 没有任何方法和属性 
class1 * c1 = [[class1 alloc]init]; 
NSLog(@"第一次:%lu",(unsigned long)[c1 retainCount]); 
//引用计数减1
[c1 release];
//输出减1之后的结果
NSLog(@"第二次:%lu",(unsigned long)[c1 retainCount]); 

我们知道,在给一个对象执行alloc方法之后,系统会在堆区为这个对象分配存储空间,引用计数初始为1,在执行release方法之后,引用计数减1,当一个对象的引用计数为0时,系统执行dealloc方法回收释放内存。
  那么上述执行结果“按道理来说”应该是:

  第一次:1
  第二次:0

或者直接崩溃,因为引用计数为0时该对象已经被回收,因为c1此时已经成为了一个野指针。
  当我在编译器中执行的时候出现了意想不到的答案,没有并崩溃,正常运行出的结果是:

  第一次:1
  第二次:1

那么我们接着看下面这个例子:

NSString *str = @"abcdefg"; 
NSLog(@"Str = %lu",(unsigned long)[str retainCount]); 

NSNumber *num = @11; 
NSLog(@"Str = %lu",(unsigned long)[str retainCount]); 

NSNumber *longNum = @222222222222222222;
NSLog(@"longNum = %lu",(unsigned long)[longNum retainCount]);

NSMutableString *str1 = [[NSMutableString alloc]initWithString:@"abcdefg"];
NSLog(@"Str1 = %lu",(unsigned long)[str1 retainCount]); 

结果是:

Str     = 18446744073709551615
num     = 9223372036854775807
longNum = 1
Str1    = 1

真相大白

Effective Objective 2.0的第36条这样解释道:

对象的引用计数非常大是因为,这些对象都是“单例对象”,所以其引用计数都很大。系统会尽可能把NSString实现成单例对象。如果字符串像例子上出现的情况这样,是个编译器常量(compile-time constant),那么就可以这样来实现了。在这种情况下,编译器会把NSString对象所表示的数据放到应用程序的二进制文件里,这样的话,运行程序时就可以直接使用了,无需再创建NSString对象。NSNumber也类似,他使用了一种叫做“标签指针”(tagged pointer)的概念来标注特定类型的数值。这种做法不适用NSNumber对象,而是把与数值有关的全部消息都放在指针值里面。运行时系统会在消息派发期间检测这种标签指针,并对它执行相应操作,使其行为看上去和真正的NSNumber对象一样。这种优化只在某些场合使用。并且这种单例对象的引用计数也不会改变。

也就是说,系统为了优化,将一些字符串作为一个“常量”直接放进二进制文件中,并不会为它分配相应的堆区内存。下次使用时也可以直接使用。这样就会减少很多分配内存,回收内存,以及管理一个对象的引用计数等复杂的操作。从而优化性能。
  
  对于NSNumber,它是一个对象类型,所以按道理来说在NSNumber *中应当是例子中存在11这个数值内存的地址。那么我们为了存放11这个简单的数字,起码要有2块内存:一个内存存放11这个数(堆区),一个存放前面那块内存的地址(栈区)。所以苹果引入了tagged pointer这个概念,将一些一些比较小的数值,也就是放指针的那块栈区放的下的数(32位系统小于4字节的数值,64位小于8字节的数值)。将11直接放在栈区那个本来放指针的地方,这样堆区就不用再分配内存给它了。但是作为我们来说,这些都是系统自动优化的。我们在使用上不会有任何的问题。关于NSNumber这个有意思的问题,可以参看巧爷的这篇文章深入理解Tagged Pointer

而且我查阅了retainCount的官方文档,原文是这样的:

描述: 不要使用该方法。
返回值: 接收机的引用计数。
特殊注意事项:
这种方法在调试内存管理问题上都是没有价值的。因为任意数量的框架对象可能保留一个对象,以持有对它的引用。而在同一时间自动释放池可持有任意数量的延迟释放的对象。所以你从这个方法并不太可能得到有用的信息。

要了解内存管理,你必须遵守的基本法则,阅读内存管理策略。要诊断内存管理的问题,使用合适的工具:

可用性
iOS 2.0及其之后版本

所以我们产生这个问题的根源就是retainCount可能对于我们来说只是一个“标识”性质的方法,我们并不能从中的得到有用的信息。

将错就错

实质上抛开retainCount的限制来说,其实上面两个例子还是有很多可以深究的地方,以下是我的一些理解:

  • 对象的引用计数可能永远不会为0,对对象的引用计数为1,执行引用计数减1的操作后,系统明白这个对象已经不被任何对象所持有,那么应该释放他了,所以他并不会再执行将引用计数减为0的操作了,因为没有必要,系统已经知道该如何处理这个对象了。多余的操作只会增加系统的负担。

啰嗦几句

其他的一些相关问题:

问题1:什么对象才会有引用计数?
答:需要回收内存的对象。换种说法就是内存分配到了堆区的对象(虽然这样说并不准确),我们使用类方法初始化一个对象不需要关心它的内存,是因为它一开始就被系统自动分配到了常量区,系统会帮我们搞定。而通过new,alloc,copy之类的都会在堆区为他们分配了新的内存,引用计数也加1,这些就是需要回收的内存,为了知道何时回收他们的内存,便有了引用计数这样的标记。就如同借钱一样,你借了系统的钱(内存),他就给你拿个小本本记着(引用计数),最后等你离开之前把钱收回去(回收释放内存)。

问题2:什么会影响引用计数?
答:
使引用计数加1的操作有:
创建并持有对象:alloc/new/copy/mutableCopy
持有对象:retain

使引用计数减1的操作有:
释放持有权: release

问题3:引用计数的内在含义是什么?
答:对象的标记,标记你对这个对象的被需求度,如果有人要用得上它,那么他就不应该被释放掉。我自己创建了一个对象,说明我肯定要用它,所以这个步骤叫做“创建并持有对象”,而不是我自己创建的对象,但我也要用到它,所以我就要“借用一下(retain)”,这个步骤被称为“持有对象”。不论是不是我创建的,只要我不用它了,我就应该释放我对这个对象的持有权,即“释放持有权”,当所有人都不要这个对象了(没有人再有该对象持有权),我们就要释放掉它。


文章水平有限,如有错误还请大家指正。
若需转载请注明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容