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
- OC09 -- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- Windows Message Queue
- MSDN中关于#pragma once的译文
- 在Eclipse上创建Android模拟器
- ubuntu 安装 python3 升级
- hdu 5291 Candy Distribution(dp)
- OC09 -- 内存管理
- pcap抓包库部分函数说明
- SYS_CONNECT_BY_PATH函数用法 ORACLE
- iOS新特性页面制作
- DOM编程的思路
- 利用css写响应式布局
- Android三种实现自定义ProgressBar的方式介绍
- TP框架中,对数据的过滤函数
- CUDA(六). 从并行排序方法理解并行化思维——冒泡、归并、双调排序的GPU实现