内存管理

来源:互联网 发布:靠谱日本代购淘宝店 编辑:程序博客网 时间:2024/06/05 06:01



一.基本原理


局部变量存放在栈中,代码块执行完毕后,系统会自动回收。而对象存放在堆里,洗头不会自动回收,需要手动回收。

1.为什么要内存管理

(1)移动设备的内存十分有限,每个app所能占用的内存是有限制的。

(2)当app所占用的内存比较多时,系统会发出内存警告,这时必须要回收一些不需要再使用的内存空间。

(3)任何继承了NSObject的对象,都需要内存管理,因为这些系统不会自动回收

(4)对其他数据类型(int,char,float,double,struct,enum),不需要内存管理,因为这个数据类型系统会自己回收。

 

2.对象的引用计数器

(1)每个OC对象都有自己的引用计数器,是一个整数,表示“对象被引用的次数”,即有多少人正在使用这个OC对象

(2)每个OC对象内部专门有4个字节的存储空间来存储引用计数器

 

3.引用计数器的作用

(1)当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1

(2)当一个对象的引用计数器值为0时,对象占用的内存就会被系统回收。换句话说,如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收,除非整个程序已经退出

 

4.引用计数器的操作

(1)对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身)

(2)对象发送一条release消息,可以使引用计数器值-1(没有返回值)

(3)给对象发送retainCount消息获得当前的引用计数器值


5.对象的销毁

(1)一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收。

(2)一个对象被销毁(回收)时,系统会自动向对象发送一条dealloc消息(调用dealloc方法)。

(3)会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言。

(4)重写了dealloc方法,就必须调用[super dealloc],并且放在最后面。

(5)不要直接调用dealloc方法,这个方法是系统在对象被销毁时自动调用的,不代表你调用了,对象就被销毁了。

(6)对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)

 

6.概念

(1)僵尸对象:所占用内存已经被回收的对象,僵尸对象不能再使用。

(2)野指针:指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错(EXC_BAD_ACCESS:

访问了一块坏的内存(已经被回收、已经不可用的内存))

(3)空指针:没有指向任何东西的指针(存储的东西是nil、NULL、0),给空指针发送消息不会报错。

 

二.内存管理原则

 

1.原则分析

(1)只要还有人在用某个对象,那么这个对象就不会被回收

(2)只要你想用这个对象,就让对象的计数器+1

(3)当你不再使用这个对象时,就让对象的计数器-1


2.如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease。换句话说,不是

你创建的,就不用你去release或autorelease。


3.只要你调用了retain,无论这个对象是如何生成的,你都要调用release或autorelease。


4.总结:

(1)有始有终,有加就有减。

(2)曾经让对象的计数器+1,就必须在最后让对象计数器-1。

(3)只要调用了alloc,必须有release(autorelease),对象不是通过alloc产生的,就不需要release


三.set方法的内存管理


1.如果你有个OC对象类型的成员变量,就必须管理这个成员变量的内存。已经内存管理的set方法:

- (void)setBook:(Book*)book {           if(book != _book) // 判断是不是新传进来的对象,防止多次传递相同的对象引起的内存泄露             {            [_book release]; // 既然有了新的对象,就需要释放原先的对象               _book = [bookretain]; // 新对象做一次retain                   }}


2.有增就改有减,需要在dealloc方法中实行内存管理:

- (void)dealloc {           [所有对象成员变量 release]; // 这个类中所有对象成员变量都需要做一次release          [super dealloc];// 后面必须调用父类的dealloc方法  }


3.总结

(1)你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作)

(2)你不想再使用(占用)某个对象,就应该让对象的计数器-1(让对象做一次release)

(3)匿名对象会导致内存泄露

(4)谁retain,谁release

(5)谁alloc,谁release

 

四.@property参数

 

1.@property由于可以自动生成set方法,所以在@property后面写括号也可以管理内存

 

2.括号里面控制set方法内存管理的参数

(1)retain:release旧值,retain新值(适用于OC对象)。

(2)assign:直接赋值,不做任何内存管理(默认,用于非OC对象类型。即使默认了,也要写,方面交流)。

(3)copy:release旧值,copy新值(一般用于NSString*)。

 

3.括号里面控制需不需要生成set方法的参数

(1)readwrite:同时生成set方法和get方法(默认)

(2)readonly:只会生成get方法


4.括号里面控制多线程管理的参数

(1)atomic:性能低(默认)

(2)nonatomic:性能高(以后就写这个)


5.括号里面控制set方法和get方法的名称

改变名称只影响方法名,不影响成员变量。

(1)setter:设置set方法的名称,一定有个冒号:(set方法接收参数,所有方法名中一定有:)。

(2)getter:设置get方法的名称(一般用在BOOL类型,返回值是BOOL类型方法名一般is开头驼峰标示)。

 

6.补充:

time_t:时间类型,从1970-01-01 00:00:00开始,一共度过了多少毫秒。



五.循环引用

1.@class

对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类。当使用#import包含各自头文件的时候,会无限循环包含,编译器会报错。解决办法是使用@class在两个类互相声明,@class只是声明这个是个类,使编译器不报错。

用法概括:使用@class 类名; 就可以引用一个类,说明一下它是一个类,编译器就不会报错。

@class和#import的区别:

(1)#import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在A.h文件中 B 只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,在实现文件中加上头文件即可,就可以知道B类中的方法和成员变量。

(2)如果有上百个头文件都#import了同一个文件,或者这些文件依次被#improt,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对来讲,使用@class方式就不会出现这种问题了。

(3)在.m实现文件中,如果需要引用到被引用类的实体变量或者方法时,需要使用#import方式引入被引用类。

 

2.循环retain

(1)A对象retain了B对象,B对象retain了A对象 ,这样会使谁都不能被回收,导致内存泄露,这样会导致A对象和B对象永远无法释放。

(2)解决方案:当两端互相引用时,应该一端用retain、一端用assign。

 

3.开发中引用一个类的规范

(1)在.h文件中用@class来声明类

(2)在.m文件中用#import来包含类的所有东西

 

六.autorelease

 

1.autorelease

(1)给某个对象发送一条autorelease消息(方法)时,就会将这个对象加到一个自动释放池中

(2)当自动释放池销毁时,会给池子里面的所有对象发送一条release消息(做一次release操作)

(3)调用autorelease方法时并不会改变对象的计数器,并且会返回对象本身

(4)autorelease实际上只是把对release的调用延迟了,对于每一次autorelease,系统只是把该对象放入了autorelease pool中,当该pool被释放时,该pool中的所有对象会被调用Release

 

2.自动释放池的创建

(1)ios 5.0后

<spanstyle="font-family:SimSun;font-size:18px;">@autoreleasepool {      代码 }</span>

(2)ios 5.0前

<spanstyle="font-family:SimSun;font-size:18px;">NSAutoreleasePool *pool= [[NSAutoreleasePool alloc] init]     代码 [pool release]; // 或 [pool drain];  </span>

(3)在ios程序运行过程中,会创建多个自动释放池,它们是以栈(先进后出)的形式存在内存中。

(4)OC对象只需要发送一条autorelease消息,就会把这个对象添加到最近的自动释放池中(栈顶的释放池)

(5)代码写在自动释放池里面,不需要写[对象 release],只需要创建完对象后面调用autorelease
(6)由于在自动释放池里面建立对象代码太长了,可以写一个类方法,调用这个类方法就可以创建一个已经autorelease的对象,这个方法中创建对象不要直接用类名,一般用self。

(7)一般来说,除了alloc、new或copy之外的方法创建的对象都被声明了autorelease,不需要再release。

 

3.autorelease的好处

(1)不用再关心对象释放的时间

(2)不用再关心什么时候调用release

 

4.autorelease的使用注意

(1)占用内存较大的对象不要随便使用autorelease

(2)占用内存较小的对象使用autorelease,没有太大影响

 

5.错误写法

(1)alloc之后调用了autorelease,又调用release

(2)连续调用多次autorelease,相当于把这个对象多次放在释放池,要做多次release。

 

七.补充

 

1.最新的Xcode建立时自动是ARC模式,如果想手动管理内容需要在Build Settings设置。

2.默认情况下,Xcode是不会管僵尸对象的,使用一块被释放的内存也不会报错。为了方便调试,应该开启僵尸对象监控:打开EditScheme-->Run-->Diagnostics-->勾选Enable ZombieObjects选项

 


0 0