OC内存管理

移动设备给其应用分配的内存是很有限的,在IOS系统中,一般会在启动时给应用分配30M的内存空间,如果一个应用超过25M,就会导致应用崩溃或者出现闪退的情况。所以,我们需要及时销毁不需要的对象,来保证程序不产生额外的内存开销。虽然在?iOS 5/ Mac OS X 10.7开始导入了ARC(自动引用计数)模式,但是在内存管理很复杂的情况下,ARC一样需要注意内存问题。

cocoa提供了一个引用计数机制来进行内存管理,引用计数就是当前内存的使用者的数量,即只有当一个对象做了alloc,copy,retain操作后,他才对该内存拥有所有权,此时该对象才可以进行释放。如果只是简单的 ?对象a = 对象b,那么,a和b指向的是同一块内存空间,a对b指向的内存不具有所有权,所有a不能进行释放。

环境信息:

Mac OS X 10.9

Xcode 5.1.1

正文:

一、内存管理法则

保证每一个要用的对象都在内存中,保证不用的对象及时清除,谁引用谁管理,谁分配谁释放 ,即:如果一个对象使用了alloc,copy,retain,那么你必须使用对应的release,或者autorelease(这句话只是一个概述,具体情况还是需要具体分析)。

alloc:为一个新的对象分配空间,并将他的引用计数置为1,调用alloc方法,即拥有对新对象的使用权。

copy:制造一个对象的副本(这个分为深copy,和浅copy,在之后的文章中会详讲),调用者具有对副本的使用权

retain:使对象的引用计数+1,并获得该对象的使用权

release:使对象的引用计数-1,并且放弃对象的使用权

autorelease:作用同release一样,不过释放是在未来某个时间

 

二、手动将工程调为非ARC模式

xcode较新一些的版本不能在创建工程的时候勾选是否使用ARC模式,所以需要在后期进行手动调为非ARC模式

这个百度下就会有很多方法,所以在这里就不多说

 

三、简单的内存管理

1.创建一个对象,先对其retain,再对其进行释放,发现alloc之后它的引用计数为1,retain之后为2,第一次release之后为1,再次release的引用计数还是为1。这是因为程序执行的速度较快,再次打印输出其引用计数时,该内存还没有被销毁,就可以打印出1,如果再打印引用计数之前,打印hello,就会存在一定几率的崩溃,因为系统在给@“hello”分配内存的时候,可能会覆盖,然后再引用person时就会报错。


Person *person = [[Person alloc] init];
NSLog(@"%ld", [person retainCount]);//alloc将引用计数置为1
[person retain];
NSLog(@"%ld", [person retainCount]);//retain将引用计数 + 1        
[person release];       
NSLog(@"%ld", [person retainCount]);//release将引用计数 - 1         
[person release];                
NSLog(@"hello"); //这样就会存在一定几率的崩溃                 
NSLog(@"%ld", [person retainCount]);//还可以打印出1 是因为还没有被销毁,但是系统在给@“hello”分配内存的时候,可能会覆盖,然后再引用person时就会报错

2.由person2持有person,那么他们都有该内存的所有权,释放是要和定义时的对象相反


Person *person = [[Person alloc] init];
Person *person2 = [person retain];
NSLog(@"%ld  %ld",[person retainCount], [person retainCount]);
[person2 release]; 
NSLog(@"%ld  %ld",[person retainCount], [person retainCount]); 
[person release]; 

四、遛狗原理:一个类当中包含另一个类时的内存管理

1,定义Dog类,并且在Person类中声明Dog这个成员变量


#import <Foundation/Foundation.h> 
#import "Dog.h"@interface Person : NSObject
{
     Dog *_dog;
     NSString *_name;
     int _age;
}
+ (Person *)person; //便利构造器
- (id)initWithName:(NSString *)name age:(int)age;//自定义初始化方法
- (void)setDog:(Dog *)dog; - (Dog *)dog;
- (void)setName:(NSString *)name;
- (NSString *)name;
- (void)setAge:(int)age;
- (int)age;
@end

2.在主函数中实例化Dog对象和Person对象


Dog *dog1 = [[Dog alloc] init];
Person *person = [[Person alloc] init];
[person setDog:dog1];        
Dog *dog2 = [[Dog alloc] init];
[person setDog:dog2];
NSLog(@"%ld", [dog1 retainCount]);
NSLog(@"%ld", [dog2 retainCount]);
[dog2 release];
[person release];
[dog1 release];

3.重新实现Person的成员变量_dog的setter。最开始的写法是_dog = dog;这样如果主函数里面的dog对象释放了,那么person也将无法使用_dog成员变量,所以,我们需要让_dog去持有dog,这样无论外界这个dog销不销毁,person对象都可以继续使用_dog这个成员变量,这样就有了第一次写法。但是在给成员变量赋值之前,我们需要先释放成员变量之前指向的内存,就有了第二次改变。但是这种做法是不严谨,考虑这种情况,如果形参dog和当前的成员变量指向的是同一块内存空间,那么我们将_dog释放了,就会造成??_dog?=[dog?retain];这句话使用了引用计数为0的变量,所以在赋值之前,我们需要对比较形参和成员变量是否相同,这样就是第三次改变。当然写法不止这一种,用方法二也是同样的效果。


- (void)setDog:(Dog *)dog  {
     _dog = [dog retain];//第一次写法
     [_dog release];  
     _dog =[dog retain];//第二次改变
     if (_dog != dog) {
          [_dog release];
          _dog =[dog retain];//第三次改变
     }
     //方法二
     [dog retain];    
     [_dog release];    
     _dog = dog;   
}

4.在Person类中重写dealloc方法。dealloc是在一个对象的引用计数为0的时候,系统自动调用的方法,这是才是真正销毁对象的方法,一般不会自己去调用。刚刚我们让成员变量持有了形参,那么我们就需要对其释放,释放的时间当然是person类被销毁时,所以,我们需要重写dealloc。dealloc里面需要对所有是对象的成员进行释放。


- (void)dealloc {
     NSLog(@"%s", __FUNCTION__); // 输出方法名
     [_dog release];
     [_name release];
     [super dealloc];
}

五、对便利构造的成员变量进行内存管理

秉着谁alloc谁释放内存的原则,在便利构造里面,我们需要对alloc出来的person进行释放,但是在释放之前,我们会返回当前创建的person对象,并且在主函数中使用。所以针对这种情况,我们使用autorelease来让系统在不使用该对象时再进行释放。所以,这就说明其他类,比如使用NSString便利构造出来的对象不需要我们去再次释放该对象,因为在其便利构造的方法里面已经有autorelease,一次alloc只对应一次release。


+ (Person *)person {
     Person *person = [[Person alloc] init];
     [person autorelease];//滞后减一
     return person;
}

六、自定义初始化方法的内存管理

方法一不用多少,和setter是一个道理。方法二中的@“name”是便利构造出来的,已经对应了一次autorelease,所以成员变量需要先持有,再在dealloc里面释放。方法三中@“name”是通过alloc出来的,所以成员变量直接指向就好,不需要再次持有它,这样刚好一个alloc,对应一个dealloc里面的release。


- (id)initWithName:(NSString *)name age:(int)age {
     self = [super init];    
     if (self) {
          _name = [name copy]; // 方法一:也可以使用[name retain]
          _name = [[NSString stringWithFormat:@"name"] retain];//方法二:不需要我们去释放@“name”
          _name = [[NSString alloc] initWithFormat:@"name"]; //方法三:这样的话就要在dealloc里面释放
          _age = age;
     }
     return self;
}

七、重写init方法的内存管理

需要注意区分哪些需要release,哪些需要retain。第一种和第三种方法都和自定义初始化中类似,第二种需要注意,因为string2是常量字符串,所以不需要retain和release,不过释放也没关系


//初始化里面的内存管理
- (id)init {
     self = [super init];    
     if (self) {                
          //第一种
          NSString *string = [NSString stringWithFormat:@"string"];
          _name = [string retain]; 
               
          //第二种
          NSString *string2 = @"string2";
          _name = string2;        
        
          //第三种
          NSString *string3 = [[NSString alloc] initWithFormat:@"string3"];
          _name = string3;    
     }
     return self;
}

总结:关于内存管理问题还有很多种没有罗列出来的情况,在今后的文章中也会再罗列一些常见的情况。

发表评论

电子邮件地址不会被公开。