OC09 -- 内存管理

来源:互联网 发布:windows 2008 pe iso 编辑:程序博客网 时间:2024/06/05 12:48

内存管理的三种方式

为什么要进行内存的管理呢?
iOS程序会出现Crash(闪退)问题,90%以上都是因为内存问题.
内存问题体现在两个方面:内存溢出 、野指针异常.
内存溢出: iOS会给每个应用程序提供一定的内存,用于程序运行.而一旦超出了内存上限,程序就会Crash.

野指针异常: 对象内存空间已经被系统回收, 却仍然使用指针操作这块内存.

一. 垃圾回收(gc):java常见的管理内存的管理内存的方法,由系统自动来检测对象是否被使用,是否被释放.
二. MRC(Manual Reference Count): 手动管理引用计数,iOS管理内存方式,程序员通过手动方式来管理对象是否被释放.
三. ARC(Auto Reference Count): 自动管理引用计数,基于MRC,系统自动的管理内存,以后还是先使用MRC,培养管理内存的习惯.

iOS支持两种内存管理方式: MRC & ARC
MRC的内存管理机制是: 引用计数.
ARC是基于MRC的.

C语言中, 是使用malloc 和 free,进行堆内存的创建和释放.堆内存只有正在使用和销毁两种状态.
而实际开发中,可能会遇到两个以上的指针使用同一块内存.C语言无法记录内存使用者的个数.

OC采用引用计数的机制管理内存,当一个新的引用指向对象时,引用计数器就递增,当去掉一个引用时,引用计数就递减.当引用计数到0时,该对象就释放掉占有的资源.

影响引用计数的方法

+ alloc

开辟内存空间,让被开辟的内存空间的引用计数变为1. 这是由 0 变为 1 的过程.

//对象被创建出来之后它的引用计数retainCount就变成1    BOY *bo = [[BOY alloc] init];    NSLog(@"%ld",bo.retainCount);//1

- retain

引用计数+1,如果内存空间之前的引用计数为1,retain之后变为2,如果引用计数是3,retain之后便为4.

//retain :对对象引用计数+1    [bo retain];    NSLog(@"%ld",bo.retainCount);//2    [bo retain];    [bo retain]; NSLog(@"%ld",bo.retainCount);//4

- copy

把某一内存区域的内容拷贝一份,拷贝到新的内存空间里去,被拷贝区域的引用计数不变,新的内存区域的引用计数为1.

-release

引用计数减1,如果内存空间之前的引用计数为4,release之后变为3,如果之前引用计数为1,release之后计数为0,内存被系统回收.

    // release:对对象的引用计数-1    [bo release];    NSLog(@"%ld",bo.retainCount);//3     [bo release];     [bo release];     [bo release];    // 当对象的引用计数从1 -> 0 的时候,会自动调用dealloc方法,dealloc才是对应对象释放的方法.    NSLog(@"%ld",bo.retainCount);//1    // 当对象调用release的时候它的引用计数是1时,就不再进行-1操作,而是直接调用dealloc.

-dealloc

dealloc是继承父类的方法,当对象引用计数为0的时候,由对象自动调用.
我们可以在dealloc中打印一句话,以验证对象引用计数是否降为0.

- (void)dealloc{    NSLog(@"对象被释放了");    [super dealloc];}

-autorelease

在未来某一时刻引用计数减1. 如果内存之前引用计数为4,autorelease之后仍为4,在未来某个时刻会变成3.

通过 autoreleasepool 控制 autorelease对象的释放.
向一个对象发送autorelease消息,这个对象何时释放,取决于autoreleasepool.
autoreleasepool的使用:

BOY *boy = [[BOY alloc] init];    [boy retain];    [boy retain];    NSLog(@"%ld",boy.retainCount);//3    //release 马上会把对象的引用计数-1,但是autorelease 会延迟对对象的计数-1.    [boy release];//2    NSLog(@"%ld",boy.retainCount);//2//    自动释放池//    只要对象用autorelease释放会把对象放入系统的自动释放池中,等出了池子的范围,对象引用技术自动-1,这个相当于java的垃圾回收,对象释放.    @autoreleasepool {        [boy autorelease];        NSLog(@"%ld",boy.retainCount);//2    }    NSLog(@"%ld",boy.retainCount);//1

以下看看NSArray / NSString / NSMutableString / NSDictionary 的引用计数.其中,NSString是特殊的.

    NSArray *arr = @[@"1",@"2",@"3",@"4"];    NSLog(@"%ld",arr.retainCount);//1    NSString *str = @"11111";    NSLog(@"%ld",str.retainCount);    // -1,代表正整数最大值,因为NSString在全局静态区.    //NSString 的对象在全局静态区,它的引用计数是-1,代表正整数最大值.    NSMutableString *str2 = [NSMutableString stringWithString:@"222222"];    NSLog(@"%ld",str2.retainCount);//1    NSDictionary *dic = @{@"1":@"2",@"3":@"4"};    NSLog(@"%ld",dic.retainCount);//1

内存管理的原则

引用计数的增加与减少相等, 当引用计数降为0之后, 不应该再使用这块内存.
凡是使用了alloc 、retain 或者copy 让内存的引用计数增加了,就需要使用release 或者autorelease让内存的引用计数减少.在一段代码中,增加和减少的次数要相等.

使用便利构造器之后,不需要对它的内存做管理,因为在返回对象时会加上一个autorelease.

    NSArray *arr1 = [[NSArray alloc] initWithObjects:@"1",@"2", nil];    NSArray *arr2 = [NSArray arrayWithObjects:@"1",@"2", nil];    [arr1 release];

copy

与-retain 不同, 一个对象要想copy,生成自己的副本,需要实现NSCopying协议,定义copy的细节(如何copy).如果类没有接受NSCopying协议而给对象发送copy消息,会引起crash.

系统的类要是实现拷贝功能,必须签订拷贝NSCopying协议,然后实现对应方法.
不可变数组/字典可以通过mutableCopy转换成可变的数组字典.
mutableCopy 拷贝出来的对象是可变的,copy是不可变的.
不可变数组:

    NSArray *arr = @[@"2",@"3",@"4",@"5"];    NSLog(@"%ld",arr.retainCount);    NSArray *newArr = [NSArray arrayWithArray:arr];    NSLog(@"%@",newArr);    NSLog(@"%ld",newArr.retainCount);

不可变 -> 可变数组:

    NSMutableArray *arr1 = [NSMutableArray arrayWithArray:arr];    NSMutableArray *arr2 = [arr mutableCopy];    [arr2 addObject:@"6"];    NSLog(@"%@",arr2);

不可变 -> 可变字典:

   NSDictionary *dic = @{@"2":@"3"};    NSMutableDictionary *mudic = [dic mutableCopy];    NSLog(@"%@",mudic);

如果自己的类想要实现copy功能,就必须先签订NSCopying,然后实现对应的协议方法,initWithZone,之后就可使用copy.
BOY.h 文件

@interface BOY : NSObject<NSCopying>@property(nonatomic, retain)NSString *name;@property(nonatomic, copy)NSString *hobby;@end

copy方法的实现:
BOY.m 文件

- (id)copyWithZone:(NSZone *)zone{    BOY *b = [BOY allocWithZone:zone];    b.name = _name;    b.hobby = _hobby;    return b;}

copy方法的实现:
main.m 文件

    BOY *boy = [BOY boyWithName:@"小新" hobby:@"飞"];    BOY *newBoy = [boy copy];    NSLog(@"%@",newBoy.name);   //boy newBoy所对应的retainCount    NSLog(@"%ld",boy.retainCount); // 1    NSLog(@"%ld",newBoy.retainCount);// 1
//!!!!copy也可以改变引用计数,但是他改变的是新对象的引用计数,而原来对象的计数不变.

对象放入容器中

    BOY *boy1 = [[BOY alloc] init];    NSLog(@"%ld",boy1.retainCount);//1    NSMutableArray *arr = [NSMutableArray arrayWithObjects:boy1, nil];    NSLog(@"%ld",[arr[0] retainCount]);//2    NSLog(@"%ld",boy1.retainCount);//2    [arr removeObjectAtIndex:0];    NSLog(@"%ld",boy1.retainCount);//1    // 当对象放入到容器Array 或 字典中时,对象会被容器进行一次持有,就是retain一次,它的引用计数会+1,主要是为了防止空指针问题.    //等对象从容器中移除时,相应地会-1. 
 // 若boy已经被释放掉, 只剩有在arr中,它会随着arr消失而消失.    BOY *boy = [[BOY alloc] init];    NSArray *arr = @[boy];    [boy release];    NSLog(@"%ld",boy.retainCount);

内存优化

BOY.m文件

// 自定义初始化.使用setter方法 :将_name 改为 self.name; _hobby 改为 self.hobby- (instancetype)initWithName:(NSString *)name hobby:(NSString *)hobby{    self = [super init];    if (self) {        self.name = name;        self.hobby = hobby;    }    return self;}// 便利构造器. 添加autorelease , 所以若是在main.m函数中发现使用便利构造器定义出的对象,不需要再release,它的内部已经实现.+ (BOY *)boyWithName:(NSString *)name hobby:(NSString *)hobby{    BOY *b = [[BOY alloc] initWithName:name hobby:hobby];    //写便利构造器时别忘了autorelease.    return [b autorelease];}//!!setter方法的内部,如何实现内存优化.- (void)setName:(NSString *)name{    if (_name != name) {        [_name release];        _name = [name retain];    }//第二种写法:   //     [name retain];//     [_name release];//     _name = name ;   }// dealloc 完整版- (void)dealloc{    NSLog(@"对象被释放了");// 把成员变量中的+1操作全部减去,不使用 . 就直接对他释放,没有延迟释放的问题.    [_name release];    [_hobby release];    [_girls release];        [super dealloc];}

在BOY.h 中添加一条属性

@property (nonatomic, retain)GIRL *girls;

main.m文件:

    BOY *b = [[BOY alloc] init];    GIRL *girl = [[GIRL alloc] init];    b.girls = girl;    NSLog(@"%ld",girl.retainCount);//2    GIRL *girl2 = [[GIRL alloc] init];    b.girls = girl2;    NSLog(@"%ld",girl.retainCount);//1    // 因为第一个是girl给了b,而当第二个girl2给了b时,因为setter方法的内部实现,girl 与 girl2 不同, 所以就将girl释放了,它就变成1了.

一个完整的类 —- Person

Person.h 文件:

#import <Foundation/Foundation.h>@interface Person : NSObject@property(nonatomic, retain)NSMutableArray *arr;@property(nonatomic, copy)NSString *name;@property(nonatomic, assign)NSInteger age;//自定yi初始化- (id)initWithName:(NSString *)name age:(NSInteger)age;+ (Person *)personWithName:(NSString *)name age:(NSInteger)age;@end

Person.m文件:

#import "Person.h"@implementation Person- (id)initWithName:(NSString *)name age:(NSInteger)age{    self= [super init];    if (self) {        self.name = name;        _age = age; // age 是NSInteger型,保存在栈区,不影响堆区内存. // 当对象创建好之后,里面的数组也会创建好,不用在外部创建,避免因为忘了而造成问题.        self.arr = [NSMutableArray array];    }    return self;}+ (Person *)personWithName:(NSString *)name age:(NSInteger)age{    Person *per = [[Person alloc] initWithName:name age:age];    return [per autorelease];}// 补上一个dealloc- (void)dealloc{    [_arr release];    [_name release];    [super dealloc];}@end
0 0
原创粉丝点击