iOS内存管理 有两种方式:MRC(ManualReference Count人工引用计数)和ARC(Auto Reference Count自动引用计数)
整个ios内存管理的核心是 引用计数.
C语言的内存管理:管理内存的开辟(malloc)和回收(free).
OC内存管理与之不同:不直接管理内存的开辟和回收,管理引用计数.通过控制引用计数的加减,来实现内存的管理.引用计数0到1表示开辟了空间,1到0表示空间被系统回收了.
我们所谓的内存管理,其实管理的是数值的加和减.间接控制开辟和回收.
学习内存管理的目的:
1.形成良好的内存管理习惯,减少甚至避免出现内存问题.
2.一旦内存出现问题,能分析和解决内存问题(解决bug)
影响 引用计数的 方法有以下几个
1. +(id)alloc;
此方法继承自根类NSObject,用于创建对象,开辟的堆空间清零,同时将对象的引用计数 设置为1.是对象引用计数从0到1的过程
Person *p1=[[Person alloc]init];
NSObject给我们提供一个查看俺对象引用计数的方法:
-(NSUInteger)retainCount;
NSLog(@"当前引用计数:%lu %p",[p1 retainCount],p1);
Person *p2=[[Personalloc]init];
NSLog(@"当前引用计数:%lu %p",[p2 retainCount],p2);
只要alloc,对象肯定不是同一.最直观的表现,对象的首地址不一样.
2.- (id)retain;
此方法继承自根类NSObject,作用是让receiver所对应的引用计数+1.并且返回receiver(对象首地址).
Person*p3=[p1 retain];
NSLog(@"当
3. - (copy);
此方法继承自根类NSObject,作用是拷贝一个receiver的副本,receiver所对应的对象引用计数保持不变,副本的引用计设置为1,返回副本的首地址.
并不是 可以向所有对象 发送copy消息,只有接受了NSCopying协议的对象才可以接收copy消息.否则,会抛出异常,产生crash.
[p2 copy];//注意此处会crash!
4. - (void)release
此方法继承自根类NSObject,作用是让receiver所对应的对象引用计数-1.无返回值.
一般来说,谁产生了引用计数增加,谁要负责引用计数减少.
比如 p1使用了alloc让引用计数增加了,当p1不再使用的时候,应该使用向p1发送release消息,让引用计数减少.
p2同理.
p3通过retain让引用计数增加了,当p3不再使用的时候,应该向p3发送release消息,让引用计数减少.
[p1release];
NSLog(@"p1 release 之后,p1的引用计数:%lu",[p1 retainCount]);
NSLog(@"p1 release 之后,p3的引用计数:%lu",[p3retainCount]);
[p3release];
注意:当一个对象的引用计数 降到0的时候,对象所占有的堆内存会被系统给回收.回收之前会先执行receiver的一个方法 -(void)dealloc;
当一个对象被销毁的时候,不能再使用这个对象了.最容易出现的问题就是 野指针异常.有时候回产生crash,有时候不会.关键要看,这块被回收的堆空间是否被别的对象使用了.如果暂时无对象使用,不会出现crash,如果已经有对象使用了这块内存,就会出现crash.
野指针出现的crash是最难找的.
p3=nil;
p2=nil;
p1=nil;//向nil发送消息是不会出现crash的,系统对nil做了处理,向nil发送消息,什么也不执行.是一个空操作.
很多开发者把 [xxrelease]; xx=nil;称作 安全释放.
5. - (id)autorelease;
此方法继承自根类NSObject.作用是 延迟发送一条release消息(延迟调用release方法).
究竟延迟到什么时候,取决于autoreleasepool.
autoreleasepool自动释放池,主要作用是处理autorelease消息.在iOS中有一个自动释放池类:NSAutoreleasePool
Person*p10=[[Person alloc] init];
Person *p11=[p10 retain];
Person *p12=[p10 retain];
NSAutoreleasePool*pool=[[NSAutoreleasePool alloc] init];
NSLog(@"%lu %lu%lu",[p10 retainCount],[p11 retainCount],[p12retainCount]);
[p10autorelease];
NSLog(@"%lu %lu%lu",[p10 retainCount],[p11 retainCount],[p12retainCount]);
[pool release];
[p10autorelease];
NSLog(@"%lu %lu%lu",[p10 retainCount],[p11 retainCount],[p12retainCount]);
运行结果:
2015-01-21 14:50:39.793 MRC[9207:1327141] 3 3 3
2015-01-21 14:50:39.794 MRC[9207:1327141] 3 3 3
2015-01-21 14:50:39.794 MRC[9207:1327141] 2 2 2
NSAutoreleasePool*pool=[[NSAutoreleasePool alloc] init];
与
[pool release];
可以看做是一个括号. 在括号里面 向对象发送 autorelease消息,才会被pool 管理.
pool先记下都是谁 执行了 autorelease消息, (就像可变数组一样,把接受autorelease消息的receiver逐一add到pool里).
当pool不在需要的时候(即:销毁时),会 向pool中记录的receiver逐一发送release消息.
@autoreleasepool {//iOS5.0之后,采用这种格式,出大括号的瞬间,会让p13执行release消息,然后p10执行release消息,autoreleasepool以栈的方式工作.
[p10 autorelease];
[p13 autorelease];
}
Copy
@interface Person : NSObject<NSCopying>
@property(nonatomic,copy) NSString *name;
@property(nonatomic,copy) NSString *sex;
@property(nonatomic,assign) NSInteger age;
- (instancetype)initWithName:(NSString *)name sex:(NSString *)sex age:(NSInteger)age;
@end
(1)这是浅拷贝 ,所谓的浅拷贝:对象是有两份的,但是 成员的内容 是公用一份
//重写方法
-(id)copyWithZone:(NSZone*)zone
{
//self就是receiver,就是被拷贝的对象.
Person *p=[[Person allocWithZone:zone]initWithName:self.name sex:self.sex age:self.age];
return p;
}
(2)这是深拷贝,所谓的深拷贝:对象是两份,成员的内容也是两份.
- (id)copyWithZone:(NSZone *)zone
{
Person *p=[[Person allocWithZone:zone]initWithName:[self.name copy] sex:[self.sex copy] age:self.age];
return p;
}
(3)伪拷贝 并未拷贝
- (id)copyWithZone:(NSZone*)zone
{
return [self retain];
}
- (instancetype)initWithName:(NSString *)name sex:(NSString *)sex age:(NSInteger)age
{
self=[super init];
if(self) {
self.name=name;
self.sex=sex;
self.age=age;
}
return self;
}
注意:copy由调用者在外面释放