内存管理
内存管理的三种方式
1.垃圾回收(gc)轮询延迟
程序员只需要开辟内存空间,不需要⽤代码显⽰地释放,系统来判断哪些空间不再被使⽤,并回收这些内存空间,以便再次分配。整个回收的过程不需要写任何代码,由系统⾃动完成垃圾回收。Java开发中⼀直使⽤的就是垃圾回收技术
2.MRC(Manual Reference Counting)人工引用计数
内存的开辟和释放都由程序代码进⾏控制。相对垃圾回收来说,对内存的控制更加灵活,可以在需要释放的时候及时释放,对程序员的要求较⾼,程序员要熟悉内存管理的机制。
3.ARC(Auto Reference Counting)自动引用计数
iOS 5.0的编译器特性,它允许⽤户只开辟空间,不⽤去释放空间。它不是垃圾回收!它的本质还是MRC,只是编译器帮程序员默认加了释放的代码。
OS X使用的是垃圾回收的方式
iOS使用的是ARC方式
内存管理的3种常见问题
1.内存泄漏
在堆区开辟了空间后, 未进行释放操作, 结果导致一直占据该内存单元, 直到程序结束。
2.内存溢出
系统的内存是有限的,若程序占用内存超出了内存上限 程序会引起闪退(crash)
3.野指针
野指针: 指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为NULL而避免,而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。
因此, OC中引入了引用计数器, 在每个对象的内存单元中都存放着一个计数器, 占据4个字节, 它用来计算对象的内存单元被指针引用的次数.
影响计数器的5个因素
1.alloc
alloc 先将指定内存空间清零 再开辟空间 引用计数 从 0 –> 1
Person *person1 = [[Person alloc] initWithName:@"long" age:18];
NSLog(@"%ld",[person1 retainCount]);
2.retain
只有retain 才能 +1 Person *per = person1 不增加计数
Person *person2 = [person1 retain];
NSLog(@"%ld",[person1 retainCount]);
Person *person3 = person2;
NSLog(@"%ld",[person1 retainCount]);
3.copy
如果要调用copy方法, 本类必须加入NSCopying协议
@interface Person : NSObject <NSCopying>
拷贝分为三种: 伪拷贝 浅拷贝 深拷贝
- (id)copyWithZone:(NSZone *)zone
{
// 伪拷贝
// 伪拷贝 拷贝完还是一个对象(直接返回)
retain self;
}
- (id)copyWithZone:(NSZone *)zone
{
// 浅拷贝
// 拷贝后 有2个对象 但是它们的值是同一个值
// 引用计数变化 被拷贝的对象不变 拷贝出来的新对象 从 0 - 1
Person *p = [[Person alloc] initWithName:_name age:_age];
return p;
}
- (id)copyWithZone:(NSZone *)zone
{
// 深拷贝
// 拷贝出新对象 并且 对象的值也重新拷贝一份 再赋值
// 引用计数变化 被拷贝的对象不变 拷贝出来的新对象 从 0 - 1
// 对字符串进行拷贝 拷贝的结果 要看字符串这个类如何实现的拷贝方法 对不可变字符串的拷贝 其实相当于直接retain一次
// 可变字符串拷贝的时候 就是真的拷贝了一个新的
// 字符串这个类 类簇
NSString *name = [_name copy];
Person *p = [[Person alloc] initWithName:name age:15];
return p;
}
4.release
release 是引用计数 -1
当对象的引用计数为0的时候 该对象就会被系统释放
Person *p1 = [[Person alloc] initWithName:@"long" age:19];
NSLog(@"%ld",[p1 retainCount]);
[p1 retain];
NSLog(@"%ld",[p1 retainCount]);
[p1 retain];
NSLog(@"%ld",[p1 retainCount]);
[p1 release];
NSLog(@"%ld",[p1 retainCount]);
[p1 release];
NSLog(@"%ld",[p1 retainCount]);
[p1 release];
// 计数器已经为0 p1 被释放 不要再去访问这个空间 一访问就是野指针 比如:
NSLog(@"%ld",[p1 retainCount]);
// 当计数器为0时 系统会自动调用dealloc方法
// 书写规范: 一定要写在类的最上面 避免忘记书写
// 系统自动调用 释放对象
- (void)dealloc
{
NSLog(@"哈哈 被释放了");
// 如果重写dealloc方法 必须要调用父类的方法
// 保证对象可以被释放
[super dealloc];
// dealloc里面写东西 都写在[super dealloc]; 的上面 防止出现野指针
}
5.autorelease
// 在未来的某一个时刻 引用计数会自动 -1
// autorelease 需要自动释放池去 -1
// autorelease 依托自动释放池去释放的
// 出了自动释放池 相当于 系统会给释放池中间调用了autorelease的对象 发送一个 release消息
Person *p1 = [[Person alloc] initWithName:@"hah" age:18];
[p1 retain];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[p1 autorelease];
[pool release];
NSLog(@"%ld",p1.retainCount);
// 自动释放池
@autoreleasepool {
// 先进后出
[p1 autorelease];
}
// 对于遍历创建对象而言 @autoreleasepool 如下放置比较好 以免内存溢出
for (int i = 0; i < 10; i++) {
Person *p = [[Person alloc] initWithName:@"long" age:18];
@autoreleasepool {
[p autorelease];
}
}
在实现便利构造器时 返回值需要调用autorelease方法 以免造成内存泄露
+ (instancetype)personWithName:(NSString *)name age:(NSInteger)age
{
Person *person = [[Person alloc]initWithName:name age:age];
return [person autorelease];
}
用便利构造器创建对象时 需要将对象创建在自动释放池之内
@autoreleasepool {
Person *per = [Person personWithName:@"sdf" age:19];
}