一、基本原理
什么是内存管理:
移动设备的内存极其有限,每个app所能占用的内存是有限制的;
当app所占用的内存较多时,系统会发出警告,这时得回收一些不需要再使用的内存空间。比如回收一些不需要的对象、变量等。
管理范围:任何继承了NSObject的对象,对其他基本数据类型(int、char、float、double、struct、enum等)无效。
栈:存放局部变量(调用完自动回收);
堆:动态产生的存储空间(需手动回收)。
二、对象基本结构
每个对象都有自己的引用计数器,是一个整数。表示“对象被引用多少次”,即有多少人在使用这个对象。
每个对象OC对象内部专门有4个字节的存储空间来存储引用计数器。
引用计数器的作用:
1.创建新对象,该计数器默认是 1;
2.当一个对象的引用计数器为0,就被回收,或退出程序;
3.给对象发送一条retain消息,可以引用计数器 +1;
4.给对象发送一条release消息,可以使计数器-1;
5.可以给对象发送retainCount消息获得当前的引用计数器值。
[p retain]; 引用计数器 +1。
注:retain方法返回的是对象本身。
[p release]; 引用计数器 -1。
野指针:指向僵尸对象(不可利用的内存空间)的指针。
当引用计数器变为0,p为野指针,这时候再往下使用p指针就会出
错。
报错:下一行再使用 [p release]; 时
经典错误:EXC_BAD_ACCESS:访问了一块坏的内存(已经被回收,已经不可用的内存),即野指针错误。
此时对象已是僵尸对象,发送retain消息还是报错。
所以要将野指针清空: p = nil;
Java中有空指针错误,但OC中不存在。
给空指针发送消息,不报错。
注:方法的基本使用
1.retain:计数器+1,会返回对象本身;
2.release:计数器-1,没有返回值;
3.retainCount:获得当前的计数器值;
4.dealloc:当一个对象要被回收的时候,就会调用
一定要在最后调用[super dealloc].要放在最后。
概念:
僵尸对象:所占用的内存已经被回收的对象,僵尸对象不能再使用。
野指针:指向僵尸对象的指针,给野指针发送消息会报错。
空指针:没有指向任何东西的指针(存储的东西是nil、NULL、0),给空指针发送消息不回报错。
多对象内存管理:
1.谁创建,谁release
如果你通过alloc、new、或者[mutable]copy来创建一个对象,那么你必须调用release或者autorelease,
换句话说,不是你创建的,就不用你去[auto]release.
2.谁retain,谁release
只要你调用了retain,无论这个对象是如何生成的,你都要调用release。
总结:
1.有始有终,有加就有减;
2.曾经让对象的计数器+1,就必须在最后让对象-1.
三、set方法内存管理
内存管理代码规范:
OC对象类型,应当要严谨点:例如
- (void)setBook:(Book *)book
{
//对当前正在使用的书(旧书)做一次release
[_book release];
//对新书做一次retain
_book = [book retain];
}
最严谨的set方法:
- (void)setBook:(Book *)book
{//判断是不是新传来的对象
If( book != _book)
{
[_book release];
_book = [book retain];
}
}
基本数据类型的set方法:
- (void)setAge:(int)age
{
_age = age;
}
dealloc方法的代码规范:(不要直接调用dealloc)
1.一定要[super dealloc],而且放在最后;
2.对当前对象(self)所拥有的其他对象做一次release。
例子: - (void)dealloc
{
[_book release];
[super dealloc];
}
- @property内存管理:
.retain:生成的set方法里,release旧值,retain新值
@property (retain)Book * book;
property参数:
1.控制set方法的内存管理:
(1)retain:release旧值,retain新值(适用于oc对象);
(2)assign:直接赋值,不做任何内存管理(默认,用于非OC对象类型);
(3)copy:release旧值,retain新值。
2.是否要生成set方法:
(1)readwrite:同时生成setter和getter的声明和实现;
(2) readonly:只会生成getter的声明和实现。
3.多线程管理:
(1)nonatomic:性能高(一般用这个);
(2) atomic:性能低(默认)。
4.setter和getter的名称(不改变成员变量):
@property (getter = abc,setter = setAbc:)int age;
[p setAbc:10]; 这种用法是对的;
[p setAge:10]; 这种用法是错的;
但是: p.age = 10;
p.abc = 10;
都是对的。
另:一般返回BOOL类型方法名一般以 is 开头:
@property (getter = isRich)BOOL rich;
p.rich = YES;
BOOL b = p.rich;
四、循环引用
对于循环依赖关系来说:比方说A类引用B类,B类引用A类。
这种代码编译会报错,当使用@class在两个类相互声明,就不会出现编译报错。
@class:
1.@class的作用:声明仅仅告诉编译器,某个名称是一个类
例如: @class Card;
2.开发中引用一个;类的规范:
(1)在.h文件中用@class来声明类;
(2)在.m文件中用#import来包含类的所有的东西。
3.两端循环引用的解决方案:
(1)一端用retain;
(2)一端用assign;
用@class的好处:
1.解决循环包含;
2.提高性能,头文件不会再随着包含的文件的改变而重新编译。
@class和#import的区别:
1.#import方式会包含被引用类的所有信息,包括被引用类的变量和方法;
@class方式只是告诉编译器在A.H文件中 B *b 只是类的声明,具体这个类里面有什么信息,这里不需要知道,等文件真正实现的时候,才会真正去查看B类中的信息;
2.如果有上百个头文件都用#import了同一个文件,或者这些文件依次被#importm,那么一旦最开始的头文件稍有改动,后面引用这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对而言,用@class就不会出现这种问题了;
3.在.m文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引用引用类。
五、自动释放池
ios 5.0以后
autorelease方法:
1.会返回对象本身。
2.调用完autorelease,对象的计数器值不变,
3.会将对象放到一个自动释放池中,
4.当自动释放池被销毁时,会对池子里的所有对象做一次release操作。
@autoreleasepool
{//开始代表创建了释放池
Person *p = [[[Person alloc] init] autorelease];
}//结束代表了销毁释放池
autorelease的好处:
1.不用再关心对象的销毁时间;
2.不用再关心什么时候调用release。
autorelease的使用注意:
1.占用内存较大的对象不要随意使用autorelease;
2.占用内存较小的对象使用autorelease,没有太大影响;
错误写法:
1.alloc 之后调用了autorelease,有调用了release;
2.连续多次调用了autorelease。、
自动释放池:
1.在ios程序运行过程中,会创建无数个池子。这些池子都是以栈结构存在的(先进后出);
2.当一个对象用autorelease方法时,会将这个对象放到栈顶的释放池。
ios 5.0以前的释放池(已淘汰)
NSAutoreleasePool *pool = [[NSAutorelease alloc] init];
Person *p = [[[Person alloc] init] autorelease];
[pool release];//[pool drain];
一般可以为类添加一个快速创建对象的类方法:
+ (id)person
{
Return [[[self alloc] init] autorelease];
}
系统有会默认自动回收的:
例如: NSString *str = @"123";
注:1.系统自带的方法里面没有包含alloc 、new、copy,说明返回的对象都是release 的
2.开发中经常会提供一些方法,快速创建一个已经autorelease过的对象; 创建对象时不要直接用类名,一般用self。
六、练习
/*
set方法内存管理
*/
#implement <Foundation/Foundation.h>
//与Car和Dog类组合
#implement "Car.h"
#implement "Dog.h"
@interface Student : NSobject
{
int _no; //学号
NSString *_name; //姓名
Car *_car; //car对象
Dog *_dog; //dog对象
}
//方法的声明
- (void)setNo:(int)no;
- (int)no;
- (void)setName:(NSString *)name;
- (NSString *)name;
- (void)setCar:(Car *)car;
- (Car *)car;
- (void)setDog:(Dog *)dog;
- (Dog *)dog;
- (void)dealloc;
@end
@implementation Student
//方法的实现
- (void)setNo:(int)no
{
_no = no;
}
- (int)no
{
return _no;
}
- (void)setName:(NSString *)name
{
//判断传进来的是不是新的对象
if(name != _name)
{
//回收旧姓名
[_name release];
//retain新姓名
_name = [name retain];
}
}
- (NSString *)name
{
return _name;
}
- (void)setCar:(Car *)car
{
//判断传进来的是不是新的对象
if(car != _car)
{
//回收旧车
[_car release];
//retain新车
_car = [car retain];
}
}
- (Car *)car
{
return _car;
}
- (void)setDog:(Dog *)dog
{
if(dog != _dog)
{
[_dog release];
_dog = [dog retain];
}
}
- (Dog *)dog
{
return dog;
}
- (void)dealloc
{
//回收每个对象
[_name release];
[_car release];
[_dog release];
[super dealloc];
}
@end
int main()
{
//创建学生对象
Student *stu = [[Student alloc] init];
//创建姓名对象
NSString *name1 = @"rose";
//创建车子对象
Car *c1 = [[Car alloc] init];
c1.wheels = 4;
//将c1传递给stu
stu.car = c1;
stu.name = name1;
//alloc一次,release一次
[c1 release];
[stu release];
return 0;
}