码迷,mamicode.com
首页 > 其他好文 > 详细

OC08 -- 内存管理

时间:2015-07-28 21:05:53      阅读:100      评论:0      收藏:0      [点我收藏+]

标签:

一. 内存管理的三种方式

为什么要进行内存的管理呢?
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

版权声明:本文为博主原创文章,未经博主允许不得转载。

OC08 -- 内存管理

标签:

原文地址:http://blog.csdn.net/gao_zi/article/details/47110973

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!