objective-c中的内存管理——引用计数、ARC与MRC(1)

为什么要内存管理

“每当做一件事情,学一个东西的时候,总要先问问为什么。”—-我自己

(所有引用当然都是我自己说的,不服咬我呀)

为什么要内存管理呢?本着”Lawrence”的思想,我们先来思考一下。
需要内存管理,无非就是当大量使用内存的时候发现内存不够用了,诶?发现有很多不用到的内存空间被占了,一些明明已经不会再用到的东西,却还占着空间。这时候就需要清理一下门户了。清理门户就要有对应的清理工具,objective-c中的内存管理机制就是这个工具。只不过更多的事情系统会帮你做了,你所要做的就只是告诉系统,什么东西你可以丢掉了,不需要用了。系统就会自动帮你清理掉这些东西。而你所操作的,就是一个叫 “引用计数” 的东西。


引用计数

百度百科对这个引用计数的解释是这样的——

在引用计数中,每一个对象负责维护对象所有引用的计数值。当一个新的引用指向对象时,引用计数器就递增,当去掉一个引用时,引用计数就递减。当引用计数到零时,该对象就将释放占有的资源。

要如何理解这个引用计数(有的人称之为持有数)呢?
大多数的书籍网站文章都举出了同样一个例子,为了跟它们不一样,我就稍微地改复杂点(瞎比比)——

假设公司的大灯有这样一个设定:
开关有无数个级别 ,从0开始。0表示关灯,1、2、3、4……都是亮灯,亮度都是一样的。
每一个人进入办公室的时候,第一件事就是把灯的数值调高,离开的时候就得把灯的数值调低。
当第一个人进入办公室的时候,他遵守规定,把灯的数值被调为1,这时候灯亮了起来。(对应灯的引用计数(持有数,下同)为1)
当第二个人进入办公室的时候,他也调了灯的数值,所以现在数值是2,灯还是亮着。(对应灯的引用计数为2)
下班的时候,第一个人离开了,于是他调低了灯的数值,变回1。第二个人离开的时候,也调低了灯的数值,此时的数值为0。这时候灯关了。(对应灯的引用计数为0,系统将会自动释放这部分内存)

那在什么情况下会触发引用计数的增加与减少呢?

在内存管理中: 自己生成的对象,自己持有。
不是自己生成的对象,自己也能持有。
不再需要自己持有的对象就是放。
不是自己持有的对象无法释放。

生成并持有对象:alloc/new/copy/mutableCopy
持有对象:retain
释放对象: release
废弃对象: dealloc
这些内存管理是在 cocoa框架 中的foundation框架类库的NSObject类担负的。

autorelease 是使对象在超出指定的生存范围时能够自动并正确地释放。

这一段我在这里抄的
(实际上是《Objective-C高级编程 iOS与OS X多线程和内存管理》上的内容)

我们可以在xcode中编写代码来进行测试。

在使用Xcode写代码之前,要先设置为非ARC

噢,既然讲到ARC,就先讲讲什么是ARC和MRC(故意拖节奏)

Automatic Reference Counting,自动引用计数,即ARC
Manual Reference Counting, 手动引用计数,即MRC

最大的区别就在于,ARC下,不需要自己进行引用计数的管理


MRC

下面上一下代码来测试一下,在MRC下引用计数的情况

  • 自己创建的对象自己可以持有,自己可以释放
//
//  main.m
//  examingOC
//
//  Created by Lawrence on 16/8/2.
//  Copyright © 2016年 YouJoin. All rights reserved.
//

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

int main(int argc, const char * argv[]) {

    Person *p = [[Person alloc]init];//此时p的引用计数为1
    NSLog(@"%d",(int)[p retainCount]);//retainCount就是持有数

    [p retain];//持有数+1
    NSLog(@"%d",(int)[p retainCount]);//打印2

    [p release];//持有数-1
    NSLog(@"%d",(int)[p retainCount]);//打印1

    [p release];//持有数-1
    //你问我为什么不打印了?等会再说
    return 0;

}
2016-08-03 14:58:49.589 examingOC[1334:300536] 1
2016-08-03 14:58:49.590 examingOC[1334:300536] 2
2016-08-03 14:58:49.590 examingOC[1334:300536] 1
Program ended with exit code: 0
  • 不是自己创建的对象自己也可以持有,不再需要持有对象可以释放
//
//  main.m
//  examingOC
//
//  Created by Lawrence on 16/8/2.
//  Copyright © 2016年 YouJoin. All rights reserved.
//

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

int main(int argc, const char * argv[]) {

    Person *p = [[Person alloc]init];//创建对象p
    NSLog(@"%d",(int)[p retainCount]);//打印1

    Person *q = [p retain];//让q持有p
    NSLog(@"%d",(int)[q retainCount]);//打印2

    [q release];//释放q
    NSLog(@"%d",(int)[p retainCount]);//打印1

    [p release];//释放p
    //还是不打印给你
    return 0;

}
2016-08-03 15:18:49.518 examingOC[1445:339738] 1
2016-08-03 15:18:49.518 examingOC[1445:339738] 2
2016-08-03 15:18:49.518 examingOC[1445:339738] 1
Program ended with exit code: 0

傲娇了一会,很好奇上面不打印的结果,打印出来会怎么样对嘛?
好咯~那就尝试打印一下?

//
//  main.m
//  examingOC
//
//  Created by Lawrence on 16/8/2.
//  Copyright © 2016年 YouJoin. All rights reserved.
//

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

int main(int argc, const char * argv[]) {

    Person *p = [[Person alloc]init];//此时p的引用计数为1
    NSLog(@"%d",(int)[p retainCount]);//retainCount就是持有数

    [p retain];//持有数+1
    NSLog(@"%d",(int)[p retainCount]);//打印2

    [p release];//持有数-1
    NSLog(@"%d",(int)[p retainCount]);//打印1

    [p release];//持有数-1
    NSLog(@"%d",(int)[p retainCount]);//打印0????

    return 0;

}

正常情况下是不是觉得最后一个打印应该是0呀?
然而结果是这样的——

2016-08-03 15:32:44.448 examingOC[1539:365746] 1
2016-08-03 15:32:44.449 examingOC[1539:365746] 2
2016-08-03 15:32:44.449 examingOC[1539:365746] 1
2016-08-03 15:32:44.449 examingOC[1539:365746] 1
Program ended with exit code: 0

这这这?不是打印了1么???说好的0呢?
看到这里说不定有的人会说:噢日,合着你整篇文章都在骗我,不看了,拉黑!
别急,我来跟你解(jiao)释(bian)一下
还是看代码——

//
//  main.m
//  examingOC
//
//  Created by Lawrence on 16/8/2.
//  Copyright © 2016年 YouJoin. All rights reserved.
//

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

int main(int argc, const char * argv[]) {

    Person *p = [[Person alloc]init];//此时p的引用计数为1
    NSLog(@"%d",(int)[p retainCount]);//retainCount就是持有数

    [p retain];//持有数+1
    NSLog(@"%d",(int)[p retainCount]);//打印2

    [p release];//持有数-1
    NSLog(@"%d",(int)[p retainCount]);//打印1

    [p release];//持有数-1
    NSLog(@"%d",(int)[p retainCount]);//打印0????1???

    [p release];//持有数-1,如果上面还是1,再减一次会怎么样呢?

    return 0;

}
2016-08-03 15:35:31.038 examingOC[1553:371051] 1
2016-08-03 15:35:31.039 examingOC[1553:371051] 2
2016-08-03 15:35:31.039 examingOC[1553:371051] 1
2016-08-03 15:35:31.039 examingOC[1553:371051] 1
objc[1553]: Person object 0x100200b60 overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug
Program ended with exit code: 0

WTF?刚才还是1现在就变成overreleased了?不行你必须给点解释!

我给的“狡辩”是这样的:
当retainCount变成0的时候,系统是会通过调用dealloc函数来进行内存释放的。
如果是在dealloc之后调用才会出现“预想中的结果”

为此我们重写一下Person中的dealloc函数,并回到main:

//
//  Person.m
//  examingOC
//
//  Created by Lawrence on 16/8/3.
//  Copyright © 2016年 YouJoin. All rights reserved.
//

#import "Person.h"

@implementation Person
-(void)dealloc{

    [super dealloc];

    NSLog(@"I`m a flag");
}
@end

//
//  main.m
//  examingOC
//
//  Created by Lawrence on 16/8/2.
//  Copyright © 2016年 YouJoin. All rights reserved.
//

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

int main(int argc, const char * argv[]) {

    Person *p = [[Person alloc]init];//此时p的引用计数为1
    NSLog(@"%d",(int)[p retainCount]);//retainCount就是持有数

    [p retain];//持有数+1
    NSLog(@"%d",(int)[p retainCount]);//打印2

    [p release];//持有数-1
    NSLog(@"%d",(int)[p retainCount]);//打印1

    [p release];//持有数-1

    NSLog(@"%d",(int)[p retainCount]);//打印0?
    return 0;

}
2016-08-03 16:00:57.606 examingOC[1737:420398] 1
2016-08-03 16:00:57.607 examingOC[1737:420398] 2
2016-08-03 16:00:57.607 examingOC[1737:420398] 1
2016-08-03 16:00:57.607 examingOC[1737:420398] I`m a flag
2016-08-03 16:00:57.607 examingOC[1737:420398] 1
Program ended with exit code: 0

啊啊啊?明明都已经dealloc了,为什么还会是1???不行了必须友尽了!你就是来逗我的!

好吧,我不是来逗你的~也许有的小伙伴看到这里的时候,早就遇到过
EXC_BAD_ACCESS了,如果这样你还能看到这里,我觉得你一定是暗恋我……

其实正确的答案应该是EXC_BAD_ACCESS。
当一个对象被释放之后,它所在的内存应该是不能被访问的,所以是永远不可能出现retainCount打印出来为0的情况的。可是为什么会出现1呢?

  • 其实这一直是一个未确定的问题,或许只有苹果自己才知道它的真正原因,不过iOS程序员们更愿意相信,是因为这一块内存地址的内容还没有被其他内容复写,所以还可以错误地访问。

只要我们在心里认为它就是0,就可以了。——我自己

除了retain/release,其实还有autorelease可以直接影响引用计数的变化。
在函数中,常常使用 return [object autorelease]; 来实现相对简单的引用计数管理。

ARC

ARC机制相对于MRC机制,在写代码的时候少了很多很多的麻烦。苹果在iOS5以后出现了这个机制,让程序员们在编程时,不再需要考虑很复杂的retain和release、autorelease的操作。而可以花更多的时间和心情在研究界面如何美化,性能如何优化上面。
在ARC机制中,只要对象有一个强引用,它就不会被释放。当一个对象不再有强引用指向它的时候,系统将会对这个对象的内存进行释放。

那么在ARC中,是如何进行内存管理的呢?在ARC中,会不会出现其他的一些内存管理的bug呢?

且听下回分解——
(没想到写一篇博客会花那么多时间诶~)
(在公司写博客,可以作死但不可以作打死,先匿了)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值