为什么要内存管理
“每当做一件事情,学一个东西的时候,总要先问问为什么。”—-我自己
(所有引用当然都是我自己说的,不服咬我呀)
为什么要内存管理呢?本着”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呢?
且听下回分解——
(没想到写一篇博客会花那么多时间诶~)
(在公司写博客,可以作死但不可以作打死,先匿了)