// iOS应用程序出现Crash(闪退),90%以上的原因是内存问题。
在一个拥有数十个甚至上百个类的工程里,查找内存问题及其困难。了解内存常见问题,能帮我们减少出错几率。
内存问题体现在两个方面:内存溢出,野指针异常。
垃圾回收:程序员只需要开辟内存空间,不需要用代码显示地释放,系统来判断哪些空间不再被使用,并回收这些内存空间,以便再次分配。整个回收过程不需要写任何代码,有系统自动完成垃圾回收。java开发一直使用的就是垃圾回收技术。
Manual Reference Count(MRC),人工引用计数:内存的开辟和释放都由程序代码进行控制。相对垃圾回收来说,对内存的控制更加灵活,可以在需要释放的时候及时释放,对程序员要求较高,程序员要熟悉内存管理机制。
Auto Reference Count(ARC),自动引用计数:iOS5.0的编译器特性,它允许用户只开辟空间,不需要释放空间。它不是垃圾回收,本质还是MRC,只是编译器帮程序员默认加了释放代码。
ARC是基于MRC。
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Student.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// c语言的内存管理
// malloc,realloc,calloc申请堆区内存
// free 释放堆区内存。
// 采用垃圾回收机制管理堆区内存。
// oc的内存管理:
// 采用引用计数进行管理。
// 主要分为两种:ARC(automatic reference counting)
// MRC(manual reference counting)
// ARC 是 ios5.0之后的语法,也是基于MRC实现的,只不过将程序员本该释放的代码交给了编译器去做。
// 影响引用计数的几个方法
// + (id)alloc;
// 创建对象,堆区分配内存空间
// Person *p = [[Person alloc]initWithName:@"ylc" age:22];
// // 打印引用计数
// //引用计数是堆区对象所特有的。
// // - (NSInteger)retainCount;
// // 一般来说不需要返回值
// [p retain];
// NSLog(@"%ld",p.retainCount);
//- (id)copy:如果是深拷贝,原对象引用计数不变,新对象引用计数 +1,产生一个新对象
//如果是浅拷贝(指针拷贝),原引用计数 +1,新对象引用计数等于原对象引用计数,且新旧对象的地址一样。
// retainCount 打印出来的引用计数永远不可能为0;
// 引用计数从 0 到 1 是申请内存,从 1 到 0 是释放内存。
// - (id)retain : 引用计数加1
// 浅拷贝
// Person *p2 = [[Person alloc]initWithName:@"zf" age:666];
// Person *p3 = [p2 copy];
// NSLog(@"%p,%p",p2,p3);
// NSLog(@"%ld %ld",[p2 retainCount],[p3 retainCount]);
// NSLog(@"%@",p3.name);
//
// // 深拷贝
// Person *p4 = [[Person alloc]initWithName:@"呵呵"age:11];
// [p4 retain];
// Person *p5 = [p4 mutableCopy];
// NSLog(@"%ld %ld",p4.retainCount,p5.retainCount);
// NSLog(@"%p %p",p4,p5);
// NSLog(@"%@ %@",p4.name,p5.name);
// Student *stu = [[Studentalloc]init];
// stu.name = @"name";
// Student *s = [stu copy];
// NSLog(@"%@",s.name);
//
// s.name = @"Name";
// NSLog(@"%@",stu.name);
// NSLog(@"%p%p",stu,s);
//
// Student *s1 = [stumutableCopy];
// NSLog(@"%p %p",stu,s1);
// s1.name = @"name";
// NSLog(@"%@%@",s1.name,stu.name);
// 释放 - (void)release 引用计数减 1,
// //Person *p6 = [[Person person]WithName:@"1" age:1]; //不用释放
// Person *p6 = [[Person alloc]init];
// Person *p7 = [p6 retain];
// Person *p8 = p6;
// [p6 release];
// [p6 release];
// 没有alloc,retain,copy 就不需要释放。
// 在同一个大括号内,有增加的地方就需要减少,增加和减少的次数相等,保持所谓的动态平衡。
//对谁 retain 就对谁release。
// 对谁 copy ,如果是深拷贝那么新旧对象都release
// 如果是浅拷贝,随便release一个(建议对谁copy,就对谁release)。
// // dealloc
// Person *p = [[Person alloc]initWithName:@"aa" age:33];
// NSLog(@"%p",p);
// // [p release];
//
// // autorelease 引用计数减1,延迟释放,至于释放的时间根据自动释放池被摧毁时间而定
// Person *p1 = [[Person alloc]initWithName:@"a" age:1];
// [p1 retain];
// NSLog(@"%ld",[p1 retainCount]);
//
// // 向对象发送autorelease消息,对象引用不会立即减少
// // 向p,p1发送autorelease消息,其实是将对象加入自动释放池,当自动释放池,当自动释放池被销毁时,逐一向对象发送release消息。自动释放池以栈的形式管理对象,先进去后销毁,后进去先销毁。
// [p autorelease];
// [p1 autorelease];
// [p1 autorelease];
// NSLog(@"%ld",p1.retainCount);
// NSLog(@"%p",p1);
//6 字符串的拷贝
//在常量区
// 采用字面量对字符串进行初始化,引用计数用%lu打印为无符号长整形最大数,如果用%ld打印为 -1.
// 常量区的字符串,不管是汉字还是其它字符,%ld 输出的retaincount都为 -1.
// 而用 Format创建的字符串,当字符串是汉字时,%ld 输出的retainCount 为 1,er其他字符只有当 length超过 9 后才输出1.
// 当 retainCount 为 -1 时,retain 不会增加引用计数的次数。
// NSString *s = @"wqeqererewrere";
// // s1当字符超过9个是,%ld输出 S1.retainCount 为1,否则为 -1.
//
// NSString *s1 = [[NSString alloc]initWithFormat:@"123456789"];
// NSLog(@"s1 = %p",s1);
// NSLog(@"s = %p",s);
// NSString *s3 = [s1 retain];
// NSLog(@"%ld %ld",s.retainCount,s1.retainCount);
// // foundation 框架下的大部分分类都遵循了NSCopying,及NSMutableCoping协议,因此我们对框架下的类操作的时候不需要遵循协议了,自定义的类需要遵循协议且实现对应方法。
// NSString *s = [[NSStringalloc]initWithFormat:@"huaaaaaaaaasfdds"];
// NSString *s1 = [s copy];
// NSString *s2 = [s mutableCopy];
// NSLog(@"%p",s);
// NSLog(@"%p",s1);
// NSLog(@"%p",s2);
// // 字符串唯一的浅拷贝就是不可变字符串的不可变拷贝。 (copy)
// [s release];
// [s1 release];
// [s2 release];
// NSMutableString *mstr = [[NSMutableStringalloc]initWithFormat:@"1232434352wwq"];
// NSMutableString *mstr1 = [mstr copy];
// NSMutableString *mstr2 = [mstr mutableCopy];
// NSLog(@"%p",mstr);
// NSLog(@"%p",mstr1);
// NSLog(@"%p",mstr2);
// [mstr release];
// [mstr1 release];
// [mstr2 release];
}
// // 之前的自动释放池写法
// NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
// Person *p = [[Person alloc]init];
// // 将对象加入自动释放池
// [p autorelease];
// // 将池对象销毁
// [pool release];
return 0;
}
//
// Person.m
#import "Person.h"
@implementation Person
// dealloc 用来释放属性对应的实例变量(对象类型)
// 当对象的引用计数从 1 变为 0 的时候会自动向dealloc发送消息。
- (void)dealloc{
NSLog(@"%@我被释放了",self);
[_name release];
[super dealloc];
}
// copy方法的内部实现会调用了copyWithZone
// 1、浅拷贝(指针拷贝)的实现
- (id)copyWithZone:(NSZone *)zone{
return [self retain];
}
// 深拷贝的实现
// 会产生一个新的地址,地址存放的内容和原对象的内容一模一样。
- (id)mutableCopyWithZone:(NSZone *)zone{
Person *p = [[Person alloc]init];
p.name = self.name;
p.age = self.age;
return p;
}
- (instancetype)initWithName:(NSString*)name age:(NSInteger)age{
if (self = [super init]) {
_name = name;
_age = age;
}
return self;
}
@end