1>关于堆栈的理解 链接:https://www.jianshu.com/p/c3344193ce02
什么行为会增加APP的内存占用
创建一个oc对象
定义一个变量
调用一个函数或者方法
内存管理范围
任何继承了NSObject的对象
对其它非对象类型无效
简单来说:
只有oc对象需要进行内存管理
非oc对象类型比如基本数据类型不需要进行内存管理
引入堆和栈的概念
所以问题就来了,为什么OC对象需要进行内存管理,而其它非对象类型比如基本数据类型就不需要进行内存管理呢?
只有OC对象才需要进行内存管理的本质原因?
因为:Objective-C的对象在内存中是以堆的方式分配空间的,并且堆内存是由你释放的,就要release
OC对象存放于堆里面(堆内存要程序员手动回收)
非OC对象一般放在栈里面(栈内存会被系统自动回收)
堆里面的内存是动态分配的,所以也就需要程序员手动的去添加内存、回收内存
1.1内存管理
什么是内存管理?是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。
内存分配
首先既然我们需要对内存进行管理,就需要知道内存是怎么分配的,是分配在哪里的?
在iOS中数据是存在在堆和栈中的,然而我们的内存管理管理的是堆上的内存,栈上的内存并不需要我们管理。
非OC对象(基础数据类型)存储在栈上
OC对象存储在堆上
引用计数
引用计数解释
引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。使用引用计数技术可以实现自动资源管理的目的。同时引用计数还可以指使用引用计数技术回收未使用资源的垃圾回收算法。
当创建一个对象的实例并在堆上申请内存时,对象的引用计数就为1,在其他对象中需要持有这个对象时,就需要把该对象的引用计数加1,需要释放一个对象时,就将该对象的引用计数减1,直至对象的引用计数为0,对象的内存会被立刻释放。
在遥远的以前,iOS开发的内存管理是手动处理引用计数,在合适的地方使引用计数-1,直到减为0,内存释放。现在的iOS开发内存管理使用的是ARC,自动管理引用计数,会根据引用计数自动监视对象的生存周期,实现方式是在编译时期自动在已有代码中插入合适的内存管理代码以及在 Runtime 做一些优化。
文艺的解释
记得在《寻梦环游记》里对于一个人的死亡是这样定义的:当这个这个世界上最后一个人都忘记你时,就迎来了终极死亡。类比于引用计数,就是每有一个人记得你时你的引用计数加1,每有一个人忘记你时,你的引用计数减1,当所有人都忘记你时,你就消失了,也就是从内存中释放了。
如果再深一层,包含我们后面要介绍的ARC中的强引用和弱引用的话,那这个记住的含义就不一样了。强引用就是你挚爱的亲人,朋友等对你比较重要的人记得你,你的引用计数才加1。
而弱引用就是那种路人,一面之缘的人,他们只是对你有一个印象,他们记得你是没有用的,你的引用计数不会加1。当你挚爱的人都忘记你时,你的引用计数归零,你就从这个世界上消失了,而这些路人只是感觉到自己记忆中忽然少了些什么而已。
MRC手动管理引用计数:
在MRC中增加的引用计数都是需要自己手动释放的,所以我们需要知道哪些方式会引起引用计数+1;
对象操作 OC中对应的方法 引用计数的变化
生成并持有对象 alloc/new/copy/mutableCopy等 +1
持有对象 retain +1
释放对象 release -1
废弃对象 dealloc -
四个法则
自己生成的对象,自己持有。
id obj0 = [[NSObeject alloc] init];
id obj1 = [NSObeject new];
非自己生成的对象,自己也能持有。
id obj = [NSArray array];// 非自己生成的对象,且该对象存在,但自己不持有
[obj retain];// 自己持有对象
不在需要自己持有对象的时候,释放。
id obj = [[NSObeject alloc] init];// 此时持有对象
[obj release];// 释放对象
非自己持有的对象无需释放。
idobj = [NSArrayarray];// 非自己生成的对象,且该对象存在,但自己不持有
[obj release];// ~~~此时将运行时crash 或编译器报error~~~ 非 ARC 下,调用该方法会导致编译器报 issues。此操作的行为是未定义的,可能会导致运行时 crash 或者其它未知行为
1.2内存分析
intmain(){
int a =10;
int b =20;
Person *p = [[Person alloc] init];
return 0;
}
分析下这个函数的内存结构
首先基本数据类型存储的是在栈上的
指针*p也是在栈上的
但是person这个对象是在堆空间的,堆里面的内存都是动态存储的,需要程序员自己去释放内存
当程序执行完之后 栈里面的系统会自动释放内存,但是堆里的不会自动释放也就是说没有指针指向person对象,但是对象person还在内存中导致内存泄漏 需要做一次release操作 [p release]
引用计数器的操作
1>给对象发送一条retain消息,可以使引用计数器+1(retain 方法返回对象本身)
2>给对象发送一条release消息,可以使引用计数-1
3>可以给对象发送一条retainCount消息,获得当前的引用计数值
对象的销毁
1>当一个对象的引用计数器的值为0的时候,那么他将销毁,其占用的内存将被系统回收
2>当一个对象被销毁的时候,系统会自动向对象发送一条dealloc消息
3>一般会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言
4>一旦重写了dealloc方法,就必须调用[super dealloc]方法,并且放在最后面调用
野指针
野指针就是指向僵尸对象(不可用内存) 的指针,给野指针发送消息就会报错
举例
person *p = [[person alloc] init]; //person对象引用计数为1
[p release]; //person对象引用计数为0
[p release]; //p现在就出现了野指针错误,此时person对象已经被完全释放不可用,出现怀内存访问
正确的写法应该是
person *p = [[person alloc] init]; //person对象引用计数为1
[p release]; //person对象引用计数为0
p = nil;//让p置空
[p release];//这样就不会报错,即使对象不存在了,因为oc对象不存在空指针错误,给空指针发送消息不会报错
僵尸对象:所占内存已经被回收的对象,僵尸对象不能使用
空指针:没有指向任何东西的指针(存储的东西是 nil / NULL /0),给空指针发送消息不会报错
乱入
假设有一个成员变量 _dog
如何访问这个成员变量
1 > _dog 直接访问成员变量
2> self->_dog 直接访问成员变量
3 self.dog :get方法 ,相当于[self dog];
4 [self dog]; get方法
MRC下set的完整实现方式
第一种:对象类型
- (void)setCar:(car *)car{
//先判断是不是新传进来的对象
if(car != _car){
//对旧对象做一次release操作
[_car release];
//对新对象做一次retain操作
_car = [car retain];
}
}
第二种:非对象类型,基本数据类型
- (void)setAge:(int)age{
_age = age;
}
set方法内存管理相关参数
1>retain :release旧值retain新值(此次适用于oc对象类型)
2>assign 直接赋值(默认,适用于非oc对象类型)
3>copy release旧值,copy新值
如果要给set方法或者get方法重新起一个名字的话
1>>>@property(nonatomic,getter=isRich)BOOL rich; // get一般会用在bool类型中
2>>>@property(nonatomic,retain,setter=person:)Person *p; //set一定要记住冒号:这才是一个完整的方法
网友评论