612,MRC的引用计数(面试点:一,1、创建一个对象,则对象的引用计数为1 2、一次retain操作,引用计数+1 3、一次release操作,引用计数-1 二,默认情况下,Xcode是不会管...

前言

iOS5.0开始,Apple有了ARC(Auto Reference Counting),ARC不同于MRC(Manual Reference Counting),它使得大部分类和自定义类不需要手动进行内存管理,它会在适当的时候回收内存,就像栈内存一样。但是作为一个ios开发者,我们需要通过MRC下的内存管理学习,加强对底层的理解。今天这篇文章讲的是ios内存管理的知识--引用计数。

堆和栈

我们知道内存是有堆和栈的,但是它们负责存储的东西不同。

栈负责存储非oc对象,也就是不继承NSOject的那些对象,和非对象类型如(int、char、float、double、struct、enum)等 ;

堆负责存储oc对象,也就是继承自NSObject的对象,包括其自带的许多类和包(UIKit等),以及自定义的类等等。

需要注意的是:栈内存是系统自动回收的,不需要程序员去管理,不存在内存泄漏的问题;堆内存是需要程序员区管理的,存在内存泄漏的问题,所以本篇文章“引用计数”是围着堆内存(前提是MRC)管理展开的。

对象的本质

在介绍下面的引用计数前,我们还需要了解对象的本质。

举个小小的例子说明:

 JJPerson *person = [[JJPerson alloc] init];//  创建一个对象

上面这段代码是最常用的创建对象的方式,通过 [[JJPerson alloc] init] 创建出一个person对象。

但实际上,person这个东西,不是对象,它只是一个指向堆内存的指针。我们见下图:

image.png

等式的右边:JJPerson是一个继承NSObject的类,当它通过 [[JJPerson alloc] init] 创建一个对象的时候,就会在堆中开辟一段内存存储创建出来的JJPerson对象。(注:我在图中标注的地址是随意写的)

等式的左边: JJPerson *person 创建一个指针指向等式右边的内存。而在这以后就可以通过 person 这个指针自由的修改对象了。

引用计数

一、[xx retain] 和 [xx release]

每个OC对象都有自己的引用计数器,它是一个整数,从字面上, 可以理解为”对象被引用的次数”,也可以理解为: 它表示有多少人正在用这个对象。

那么上面的真实情况.png就变成了下面这样:

image.png

oc对象被创建出来的时候,他的引用计数为1,所以图中JJPerson对象的retainCount = 1。需要注意的是,当引用计数为0 时,当前的对象的堆内存就会被系统回收。

下面我们通过一些代码和代码的解释,来更加深入地理解引用计数。

#import <Foundation/Foundation.h>
#import "JJPerson.h"

int main(int argc, const char * argv[]) {
   
//    创建一个对象,则对象的引用计数为1
    JJPerson *person = [[JJPerson alloc] init];
    NSLog(@"%zd",[person retainCount]);
    
    return 0;
}

[person retainCount] 方法的作用是获取对象的引用次数,但是你会发现,编译器并没有提示你这个方法,而且会报错。这是因为iOS5.0以后默认都是开启ARC的,所以我们要做的是关闭ARC。

image.png

关闭以后我们的程序就可以正常编译通过了,打印如下:
2018-05-24 15:57:03.812508+0800 引用计数[963:61201] 1
刚好印证了刚才的“oc对象被创建出来的时候,他的引用计数为1”这句话。

接下来我们将调用[person retain][person release] 两种方法,前一个方法的作用是让对象的引用计数+1,后一个方法的作用是让对象的引用计数-1。见如下代码:

(打印结果我就直接在 NSLog 后面以注释的形式给出)

#import <Foundation/Foundation.h>
#import "JJPerson.h"

int main(int argc, const char * argv[]) {
   
//    创建一个对象,则对象的引用计数为1
    JJPerson *person = [[JJPerson alloc] init];
    NSLog(@"%zd",[person retainCount]);//    打印“1”

    
//    一次retain操作,引用计数+1
    [person retain];
    NSLog(@"%zd",[person retainCount]); //    打印“2”

//    一次release操作,引用计数-1
    [person release];
    NSLog(@"%zd",[person retainCount]);//    打印“1”

    return 0;
}

小结:

1、创建一个对象,则对象的引用计数为1
2、一次retain操作,引用计数+1
3、一次release操作,引用计数-1

二、野指针 和 空指针

当我们将引用次数为1的对象再做一次release操作,再打印,会怎么样?
答:person指针所指的对象,也就是堆内存上的对象会被回收,此时的person称为野指针,调用任何方法会使程序崩溃。

我们接下去看代码:

#import <Foundation/Foundation.h>
#import "JJPerson.h"

int main(int argc, const char * argv[]) {
   
//    创建一个对象,则对象的引用计数为1
    JJPerson *person = [[JJPerson alloc] init];
    NSLog(@"%zd",[person retainCount]);//    打印“1”

//    一次retain操作,引用计数+1
    [person retain];
    NSLog(@"%zd",[person retainCount]); //    打印“2”

//    一次release操作,引用计数-1
    [person release];
    NSLog(@"%zd",[person retainCount]);//    打印“1”

//    一次release操作,引用计数-1
    [person release];
    NSLog(@"%zd",[person retainCount]);//   打印“1”,为什么?

    return 0;
}

为什么和我刚刚说的不一样?不仅没报错而且还打印“1”?
答: 默认情况下,Xcode是不会管僵尸对象(已经被销毁的对象)的,使用一块被释放的内存也不会报错。为了方便调试,应该开启僵尸对象监控。

image.png
image.png

然后程序就“正常的”如我们所料的报错了。

2018-05-24 16:09:28.674424+0800 引用计数[1100:75090] *** -[JJPerson test]: message sent to deallocated instance 0x1004128a0

编译器说不允许发送消息给已经释放的对象,所以说当野指针 person 还想使用[person retainCount] 这个方法的时候,程序就崩溃了。

既然野指针不行,那我们让 person = nil , 再调用方法,会不会崩溃?
答:不会。person = nil,那么person指针就称为空指针,空指针可以调用对象方法,但空指针调用方法时什么都不做,毕竟原来指向的那个对象完了当然就没办法干活了,而且也不会报错。(我们可以自己在 JJPerson 类里写一个测试方法,然后在main.m 上调用一下,结果就是前面我说的”什么都没发生“,在这里就不上代码了)

小结:
内存管理不当:
1、不再使用的对象未被回收,就会造成内存泄漏,程序会闪退
2、正在使用的对象被回收,会造成野指针,访问野指针会造成程序崩溃
3、空指针可以调用对象方法,但空指针调用方法时什么都不做,而且也不会报错

三、dealloc

当对象的 retainCount = 0 时,便会自动调用NSObject的方法 dealloc 回收堆内存,我们可以在 JJPerson类中重写这个方法,见如下代码:

#import "JJPerson.h"
@implementation JJPerson

-(void)dealloc {
    
    NSLog(@"对象被释放了");
  
    [super dealloc];
}
@end

是不是很奇怪 [super dealloc] 要放在最后,这是因为NSObject 的 dealloc 才是真正进行内存回收的代码,如果你一开始就调用了 [super dealloc] ,那就没有后面的什么事了,方法肯定不会往下走, NSLog(@"对象被释放了")就别想打印出来了。
相似的道理,dealloc 这个方法应该避免主动调用,它是个回收对象的方法,你想想你一个对象调用它,是不是很矛盾,这样做也是防止带来许多的问题。

那么当我在 person 的 retainCount = 1 的时候,再次调用 [person release] ,出来的结果是:
2018-05-24 17:36:01.154622+0800 引用计数[1844:181200] 对象被释放了
说明系统已经自动调用了dealloc 方法回收内存了。

小结:
1、对象的引用计数为0 ,自动通过 dealloc 方法回收内存
2、[super dealloc] 才是真正回收内存的方法,必须在dealloc 方法的最后调用

注意:super dealloc才是释放内存的地方

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

推荐阅读更多精彩内容