OC+1-内存管理

来源:互联网 发布:青花瓷 知乎 编辑:程序博客网 时间:2024/05/31 19:47
内存管理的基本概念及范围
内存管理:系统会向app发送memory waring消息,收到消息后,需要回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等,否则程序会崩溃。
管理范围:管理任何继承NSObject的对象,对其他的基本数据类型无效(对象和其他数据类型在内存的存储位置不一样)
内存管理主要是对堆区中对象的内存管理

内存管理的原理及分类
原理
对象的所有权及引用计数任何对象都可能拥有一个或多个所有者,只要一个对象至少还拥有一个所有者,它就会继续存在
引用计数器的作用:在每个OC对象的内部,都有专门8个字节的存储空间来存储引用计数器,判断对象要不要回收(存在一种例外,对象值为nil,引用计数为0,但不回收空间)
对引用计数器的操作1、retain消息:是计数器+1,该方法返回对象本身
                                        2、release消息:是计数器-1(并不代表会释放)
                                        3、retainCount消息:获得对象当前的引用计数器值
对象的销毁:当一个对象的引用计数器为0时,name它将被销毁,其占用的内存被系统回收。当对象被销毁时,系统会自动向对象发送dealloc消息,一般会重写dealloc方法。一旦重写dealloc方法,必须调用[super dealloc],并且放在代码块的最后调用(不能直接调用dealloc方法)。
一旦对象被回收了,那么该空间就不可再用,坚持使用会导致程序崩溃(野指针错误)。
当使用alloc、new和copy出的对象,默认引用计数器为1

内存管理分类
OC提供了三种内存管理方式:1、Mannul Reference Counting(MRC,手动管理,在开发iOS4.1之前的版本的项目时,我们要自己负责使用引用计数来管理内存,比如要手动retain、release、autorelease等,而在其后的版本可以使用ARC,让系统自己管理内存)
     2、automatic reference counting (ARC,自动引用计数)
     3、garbage collection(垃圾回收,iOS不支持垃圾回收)
开发中如何使用:需要理解MRC,但实际使用时尽量用ARC

手动内存管理快速入门
//person实例一个对象
       
Person *p = [Person new];
       
//证明p有一个所有者
       
NSUInteger count = [p retainCount];//用一个8字节无符号long类型来存储
       
NSLog(@"count = %lu", count);
       
       
//是引用计数器+1
       
//p的所有者增加1,让p1去指向
       
Person *p1 = [p retain];
       
NSLog(@"p1.retainCount = %lu", [p1retainCount]);
       
       
//回收对象,使retainCount == 0
        [p
release];
       
NSLog(@"p.retainCount = %lu", [pretainCount]);
        [prelease];//p执行后,此时对象被回收销毁,自动调用dealloc方法

重写dealloc方法
//dealloc方法,是对象的临终遗言的方法
//对象被销毁的时候,会默认的调用该方法
//注意:dealloc方法是系统自动调用的,不要手动调用
-(
void)dealloc{
   
//先释放子类子集的对象空间
   
NSLog(@"Person已经挂了");
   
//再释放父类的
    [
super dealloc];
}

内存管理的原则
内存管理:
       对象如果不使用了,就应该回收他的空间,防止造成内存泄露
内存管理的范围:
       所有的继承了NSObject的对象的内存管理,基本数据类型除外
内存管理的原则:
        1、原则:只要还有人使用某个对象,该对象就不能被回收
               只要你想使用这个对象,就应该让这个独享的引用计数器+1retain
               当你不想使用时,应该让对象的引用计数-1release
        2、谁创建,谁release
        3、谁retain,谁release
        4、总结:有始有终,有加就有减
内存管理研究的内容:
        1、野指针:1)定义的指针变量没有初始化
                 2)指向的空间已经被释放了
        2、内存泄露:栈区的P已经释放了,而指向的堆区的空间还没释放,堆区的空间就被泄露了

//创建:newalloccopy
       
Dog *bigYellowDog = [Dognew];//1
       
NSLog(@"bigYellowDog.retainCount = %lu", [bigYellowDogretainCount]);
       
Dog *jd = [bigYellowDog retain];//新的对象,两个对象地址一样
       
NSLog(@"jd.retainCount = %lu", [jdretainCount]);
       
//<Dog: 0x100206760>,<Dog: 0x100206760>
       
NSLog(@"%@,%@", bigYellowDog, jd);
       
       
//谁创建,谁release
        [bigYellowDog
release];
        [jdrelease];
最后,当retainCount == 0,系统自动调用dealloc方法

单个对象内存管理(野指针)
Xcode在默认情况下,为了提高效率,不会开启僵尸对象检测Zombie->Edit scheme->run->
        Dog*byd = [Dognew];
        [bydeat];
       NSLog(@"byd.retainCount = %lu", byd.retainCount);//可以使用.调用此方法
       
        [bydrelease];//逻辑上释放,物理上不可能删除,已经被释放的对象就是僵尸对象
       
       //这就是野指针使用,已经释放了的空间。但是让僵尸对象去eat,不合理
       //默认是不报错的,设置开启僵尸对象检测
        [bydeat];
   //同理,用已经释放的僵尸对象访问retainCount方法读取引用计数器的值,也是野指针使用
       NSLog(@"byd.retainCount = %lu", byd.retainCount);
注意:
1、
nil:是一个对象值,赋值为空,便是nil
Nil:是一个类对象值
NULL:是一个通用指针(泛型指针)
[NSNull null]:是一个对象,用在不能使用nil的场合

2、不能使用僵尸对象,使其死而复生
     [drelease];
//僵尸对象死而复生,并未开启Zombie,不会报错
        [d
retain];
       
//nil nil发送任何消息都没有效果
       
//避免使用僵尸对象的方式:对象释放完了以后,给对象赋值为nil,提高程序健壮性
        d =nil;
        [dretain];//再使用僵尸对象,就不会报错,不会响应任何消息
3、野指针操作

单个对象的内存泄露问题
1、创建和使用完后,没有release
2、没有遵守内存管理原则,新增的retain要和release对应
3、不当的使用nil,在release之前,使用nil赋值。导致最后并没有release,因为nil不会响应任何消息
4、在方法的内部retain,但是在main中只release一次,也满足了内存管理的原则,此时的retain比较隐蔽,要注意


多个对象的内存管理(野指针)
        Person*fengjie = [Personnew];
       Car *bigBen = [Car new];
        bigBen.
speed= 180;
       
       
//给凤姐一辆车
        [fengjie
setCar:bigBen];
        [fengjie
goLasa];
       
        [bigBen
release];//1->0  Car销毁
 
       
//这句话报错的原因  goLasa方法中使用了_car(就是bigBen)bigBen已被销毁
       
//要想下面的语句不报错,必须保证_car存在
       //要想在[bigBen release]之后,_car还存在 必须bigBen的引用对象不止一个
        //即至少[bigBen retain]一次,但是这种方法明显比较笨
       //还可以在setCar方法里这样赋值_car = [car retain];这样比较健壮
        //但是这样之后,依旧存在[bigBen release]的问题
       //为了保证,人没亡,就可以调用车
       //我们可以在dealloc方法里进行[bigBen release]-[_car release]
        [fengjiegoLasa];

set方法的内存管理
原则:在一个类中,有其他类的对象(关联关系)
基本数据类型作为实例变量:直接赋值_speed = speed;
对象类型作为另一个类的实例变量:先判断是否是同一个对象,然后先release旧值,在retain新值
     -(void) setCar:(Car*)car {
   //如果_car == car就是同一个对象
   
//同一个对象,release之后就不可以在retain了,否则zombie就复活了
   
if (_car != car) {
       
//先释放前一个车,release旧值
        [_carrelease];
        //retain新值,并且赋值给实例变量
       _car = [car retain];
    }

@property参数
4.4之前
1)@property+手动实现
2)@property int age; + @synthesize age;//get和set方法的声明和实现都帮我们做了
3)@property int age; + @synthesize age = _age;//指定值赋值
4.4增强
@property int age;
1)生成_age
2)生成_age的get和set方法的声明和实现

格式:@property(参数1,参数2。。)数据类型 方法名
参数类别:
1、原子性:1)atomic:对属性加锁,多线程下线程安全,默认值
         2)nonatomic:对属性不加锁,多线程下不安全,但是速度快
2、读写属性:1)readwrite:生成setter和getter方法,默认值
          2)readonly:只生成getter方法           
3、set方法处理:1)assign:直接赋值,默认值
               2)retain:先release原来的值,再retain新值
               3)copy:先release原来的值,再copy新值

替换set和get方法名称:@property(setter = isVip:,getter = isVip)
//setVip: == isVip:
//vip == isVip
//同样可以使用点语法

@class的使用
#import作用:把要引用的头文件内容,拷贝到写#import处
//如果引用的头文件内容发生变化,则引用到这个文件的所有类就需要重新编译,效率低
@class的使用
     格式:@class 类名;
     @class XXX;
     含义:告诉编译器,XXX是一个类,至于类有哪些属性和方法,此处不去检测
     好处:如果XXX文件内容发生变化,不需要重新编译
    注意:由于不知道属性和方法,所以不可以直接在其他类中去使用,如何要使用,需要在实现文件中导入该类头文件

所以,在实际开发中,.h中使用@class,.m中使用#import
     1).h  @class  XX;
     2).m  #import “XX.h"
     3)@class解决循环引入问题:交叉引用,循环依赖,使用#import会报错

循环retain的问题
Person*p = [Personnew];
       Dog *d = [Dog new];
        p.dog = d;    //p,d互掐
        d.owner= p;
       
        [d
release];
        [prelease];
        [prelease];//虽然也可解决问题,但有可能由于顺序不同,导致某个对象多释放一次

循环的retain会导致两个对象都会内存泄露
推荐的方法:一端使用assign直接赋值,一端使用retain

NSString类的内存管理
@“abc是字符串常量
栈区高地址,栈区-》堆区-》BSS-》数据区-》代码区
//定义字符串
       //字符串的常量池
       //如果你需要的字符串在常量池已经存在了,不会分配内存空间
       //使用字符串的时候,
       //@"abc"   stringWithString  alloc initWithString 都在常量区
       //str2str4如果在常量区,地址应该一样
       //但是地址不一样,在堆区
       NSString *str1 = @"abc";//常量区
       NSString *str2 = [NSString stringWithFormat:@"aaa"];
       NSString *str3 = [NSString stringWithString:@"abc"];//常量区
       NSString *str4 = [[NSString alloc] initWithFormat:@"aaa"];
       NSString *str5 = [[NSString alloc] initWithString:@"abc"];//常量区
       
       NSLog(@"str1 = %@,%p,%lu", str1, str1, str1.retainCount);
       NSLog(@"str2 = %@,%p,%lu", str2, str2, str2.retainCount);
       NSLog(@"str3 = %@,%p,%lu", str3, str3, str3.retainCount);
       NSLog(@"str4 = %@,%p,%lu", str4, str4, str4.retainCount);
       NSLog(@"str5 = %@,%p,%lu", str5, str5, str5.retainCount);
危险用法
retainCount对于系统有时候不准,自己的对象要把握retain和release呼应
while([a retainCount] > 0){
     [a release];
}//死循环

autorelease基本使用
1、什么是autorelease?
自动释放池:1)在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的
          2)当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中
iOS5.0之后,创建方式
     @autoreleasepool
     {//开始
     。。。。
     }//结束
autorelease:是一种支持引用计数的内存管理方式
它可以暂时的保存某个对象,然后在内存池自己排干的时候对其中每个对象发release消息(每个对象只发送一次),注意,这里只是发送release消息,如果当时的retaiCount依然不为0,则该对象不会被释放。可以用该方法保存某个对象,也要注意保存之后要释放该对象。
自动释放池的使用
1)创建自动释放池
2)加入自动释放池,对象的引用计数不会改变
     [对象 autorelease];
3)自动释放池销毁,会对池子里的所有对象release一次
2、为什么会有autorelease?
OC内存管理原则:谁申请,谁释放。如果一个方法需要返回一个对象,该对象合适释放?方法内部是不会写release来释放对象的,因为这样立即释放会返回一个空对象;调用者也不会主动释放该对象,因为遵循“谁申请,谁释放”的原则。那么此时,就发生了内存泄露。
针对这种情况,OC设计了autorelease,既能保证对象正确释放,又能返回有效地对象
autorelease好处
1)不需要关心对象释放时间
2)不需要关心什么时候调用release
3、autorelease的原理?
autorelease实际上只是把对release的调用延迟了,将对象一直保留到autoreleasepool释放时,pool中的所有对象都会调用release
4、autorelease什么时候被释放?
1)5.0之前手动释放
2)5.0之后自动释放

autorelease的注意及错误使用
1)并不是所有放到自动释放池中的代码,产生的对象就会自动释放。如果需要释放,必须加入自动释放池
          Person*p = [[Personnew]autorelease];
2)如果对象调用了autorelease,但是调用的时候,没有放在任何一个自动释放池中,此对象也不会被加入自动释放池
3)尽量不要把内存较大的对象放到自动释放池中
4)不要在一个释放吃中,多次使用[p autorelease];
5)在alloc后,自动释放池结束之后,又进行[p autorelease]操作,不允许。zombie操作

autorelease的应用场景
1、快速创建对象的类方法
Person*p = [Personperson];
       
//person类方法:快速创建对象,并且管理对象的内存(加入自动释放池)
       
//1)创建一个对象  P
       //2)用完之后,系统把对象释放掉
+(id) person
{
   
//能够创建对象
   
//能够帮我们把对象加入自动释放池
   
return [[[Person alloc] init] autorelease];
   
}
2、完善快速创建对象的方法
//创建一个学生对象
       
Student *stu = [Student person];//返回的是Person类型
        [sturun];//动态类型,程序运行时才知道是什么类型,其实是[Person run];
只有将类方法修改一下才可以
+(id) person
{
   
//能够创建对象
   
//能够帮我们把对象加入自动释放池
   
//谁调用 返回谁
   
//Person调用 返回[Person run];
   
//Student调用 返回[Student run];
   
//Person---self
   
return [[[self alloc] init] autorelease];
   
}
3、最终完善类方法
NSString*str = [Studentperson];
NSLog(@"str.lenth = %ld", str.length);
没完善之前,返回值类型是Student,指针类型是NSString。但是编译不会报错
将id类型改为instancetype类型能够智能的检测指针类型和返回值类型是否一致
+(instancetype) person
{
   
//能够创建对象
   
//能够帮我们把对象加入自动释放池
   
//谁调用 返回谁
   
//Person调用 返回[Person run];
   
//Student调用 返回[Student run];
   
//Person---self
   
return [[[self alloc] init] autorelease];
   
}
当我们需要指定值的创建对象和初始化,我们可以
-(instancetype) initWithAge:(int)age
{
   
//1、先初始化父类的,并且判断是否成功
   
if (self = [super init]) {
   
//2、初始化子类
       
_age = age;
    }
   
//3、返回self
   
return self;
}
//+(instancetype) student
//{
//    return [[self alloc] initWithAge:18];
//}
//这样就写死了
+(
instancetype) studentWithAge:(int)age
{
   
return [[self alloc] initWithAge:age];
}
此时的类方法,可以传递参数
0 0
原创粉丝点击