一 、 内存管理的方式.
iOS 应用程序出现 Crash(闪退,崩溃), 90% 以上的原因是内存问题.
在一个拥有数十个甚至上百个类的工程里, 查找内存问题极其困难. 了解内存常见问题.能帮我们减少出错几率
内存问题体现在两个方面: 内存溢出, 野指针异常.
野指针异常: 指的是 对象的内存空间已经被系统回收, 但仍然使用指针操作这块内存.
了解内存管理,能够帮我们提升程序性能,大大减少调试 bug 的时间.
内存的管理方式:
- 垃圾回收( gc ) :程序员只需要开辟内存空间, 系统会自动判断并回收不再被使用的空间,整个过程不需要在写代码
- MRC (Manual Reference Count) :人工引用计数, 内存的开辟和释放都由程序代码进行控制. 对内存的控制更加灵活.在需要内存时,及时释放.
- ARC (Auto Reference Count): 自动引用计数: iOS5.0的编译器特性, 它允许用户只开辟空间, 不用去释放空间. 他不是垃圾回收, 它的本质还是 MRC 只是编辑器帮程序员默认加了释放的代码
iOS的内存管理
iOS 支持两种内存管理方式: ARC 和 MRC.
MRC 的内存管理机制是:引用计数.
ARC 本质还是是基于 MRC 的.
什么是引用计数
在 C 语言中,使用 malloc 和 free, 进行堆内存的创建和释放.堆内存只有正在使用和销毁两种状态.
但在实际开发中,我们常常会遇到两个以上的指针同时使用同一块内存, C 语言无法记录内存使用者的个数.
OC 采用引用计数机制来管理内存,当一个新的引用指向对象时, 引用计数器就会递增, 当去掉一个引用时,引用计 数就会递减, 当引用计数到 0 时, 该对象就将释放占有的内存资源.
// 创建一个 person 对象 , 对象的的引用计数器变为1. 这是由 0 到 1 的过程
Person *person1 = [[Person alloc] init];
NSLog(@"%ld", person1.retainCount); // 引用计数器为 1
// 减掉一个引用计数
[person1 release]; // 引用计数器 - 1
person1 = nil
NSLog(@"%lu", person1.retainCount); // 引用计数器 打印结果为1 但实质是0 因为对象 person1 所指向的内存空间已经被释放, 并且要将 person1 指向空.让其安全释放
影响引用计数的方法
+ alloc : 开辟内存空间, 让被开辟的内存空间的引用计数变为1. 这是由0 到 1 的过程.
// 创建一个 person 对象 , 对象的的引用计数器变为1. 这是由 0 到 1 的过程
Person *person1 = [[Person alloc] init];
NSLog(@"%ld", person1.retainCount); // 引用计数器为 1
- retain: 引用计数加 1
Person *person2 = [[Person alloc] init]; // 引用计数为1
// 引用计数 + 1
[person2 retain];
NSLog(@"%lu", person2.retainCount); // 现在引用计数为2
如果要在在 main 函数中使用 copy 方法. 实质使用的是 - (id) copyWithZone: (NSZone *)zone
所以必须遵守的是<NScopying>协议,在.h 父类名后写上<NScopying> 并且 实现协议中的方法. copyWithZone
// 要想实现对象的拷贝,第一步就是在.h文件中遵守<NSCopying>协议
@interface Person : NSObject <NSCopying>
<pre name="code" class="objc">// 2. 实现<NSCopying>协议中的方法,copyWithZone:
- (id)copyWithZone:(NSZone *)zone
{
// 创建一个和self一模一样的对象返回
Person *p = [[Person alloc] init];
p.name = self.name;
p.age = self.age;
return p;
}
- release: 引用计数减1, 如果内存空间之前 引用计数为4,release 后引用计数变为3,如果.之前引用计数为1.release 之后变为0 内存被系统回收
// 引用计数 - 1
[person2 release]; // 2
[person2 release]; // 1 person2内存空间被释放.
person2 = nil; // 将其指向空.安全释放
NSLog(@"%lu", person2.retainCount);
- dealloc: 是继承自父类的方法, 当对象引用计数为0的时候, 由对象自动调节.
#pragma mark - 重写 dealloc 方法.
- (void)dealloc
{
NSLog(@"%@ 我被销毁了.", self);
// 使用从父类继承过来的方法,销毁从父类继承过来的属性或者实例变量
[super dealloc];
}
- autorelease :在未来的某一时刻引用计数 减1.
- autoreleasepool:自动释放池控制 autorelease 对象的释放
向一个对象发送 autorelease 消息 , 这个对象何时释放, 取决于 autoreleasepool 何时结束.
@autoreleasepool {
Person *person3 = [[Person alloc] init]; //引用计数 为 1
NSLog(@"%@", person3);
[person3 autorelease]; // 给 person3 发送 autorelease 消息
NSLog(@"%ld", person3.retainCount);
// 一直到自动释放池结束后. person3 被释放
}
自动释放池里.可以不用把 person3 置空.
@autoreleasepool {
Person *p = [[Person alloc] init]; //retainCount 为1
[p retain]; //retainCount 为2
[p autorelease]; //retainCount 为2 延迟 未来的某个时刻释放
[p release]; //retainCount 为 2 - 1 = 1
// 自动释放池结束后 p 对象内存被释放
}
出了大括号,自动释放池,才向各个对象发送 release 消息
内存管理的原则
@autoreleasepool {
Person *p4 = [[Person alloc] init];
NSLog(@"%@", p4);
[p4 autorelease];
@autoreleasepool {
Person *p5 = [[Person alloc] init];
NSLog(@"%@", p5);
[p5 autorelease];
Person *p6 = [[Person alloc] init];
NSLog(@"%@", p6);
[p6 autorelease]; //p6 先被释放
}
Person *p7 = [[Person alloc] init];
NSLog(@"%@", p7);
[p7 autorelease];
}
内存管理的原则
引用计数的增加和减少必须相等. 当引用计数降为0之后, 不应该再使用这块内存空间
凡是使用了. alloc, retain 或者 copy 让内存的引用计数增加了, 就需要是使用 release 或者 autorelease 让内存的引用计数减少. 在一段代码内, 增加和减少的的次数必须要相等.
copy 方法
- 跟 retain 不同, 一个对象想要 copy, 生成自己的副本,需要实现 NSCopying 协议,定义 copy 的细节,(如何 copy)
如果类没有接受 NSCopying 协议而给对象发送 copy 消息,会引起 crash
例如: Person 类copy 方法的实现
Person.h
// 要想实现对象的拷贝,第一步就是在.h文件中遵守<NSCopying>协议
@interface Person : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
// 2. 实现<NSCopying>协议中的方法,copyWithZone:
- (id)copyWithZone:(NSZone *)zone
{
Person *p = [[Person alloc] init];
p.name = self.name;
p.age = self.age;
return p;
}
main 函数中
Person *p1 = [[Person alloc] init];
p1.name = @"123";
p1.age = 12;
// 向p1 发送 copy 方法.赋值给 p2
Person *p2 = [p1 copy];
NSLog(@"%@", p2);