1.内存管理中的基本问题
1.1为什么要进行内存管理 分配在堆空间中的对象 需要手动去释放
回顾堆栈的区别 生命周期
栈空间 函数 函数中局部变量 调用函数压栈 函数调用结束 释放
数据段 静态变量 全局变量 程序开始 程序结束 释放
堆: malloc alloc 程序猿手动释放 free() release
1.2内存管理的定义
内存管理就是确保开辟的堆空间被正确的释放。
内存管理中的问题:
1.内存泄露 堆空间没有释放
2. 内存崩溃 野指针 (过早释放:使用已经释放的空间
重复释放:重复释放同一个空间)
1.3 C语言内存管理的缺陷
1 释放一个堆 ,必须保证所有使用堆的指针结束使用,避免(提前释放);
2 释放一个指针,确保指向同一个堆的指针,只有一个被释放,避免(重复释放);
3 模块化分工编程,不易明确谁来释放
4 多线程操作,不能确定哪个线程最后结束
1.4 OC内存管理的基本原则
1.对象在完成创建的同时,内部会自动创建一个引用计数器,这个计数器,是系统用来判断是否回收对象的唯一依据,当我们的引用计数retainCount = 0的时候,系统会毫不犹豫回收当前对象
2.[对象 retain] reatinCount + 1 ,返回self
3.[对象 release] reatinCount - 1
4.我们的引用计数retainCount = 0时 对象就被销毁了
5.dealloc函数,当一个对象要被销毁的时候,系统会自动调用dealloc函数,通知对象你将要被销毁
内存管理原则(配对原则):只要出现了 new,alloc,retain,就一定配对出现一个release,autorelease
1 Person * p = [[Person alloc] init]; 2 NSLog(@"%lu",p.retainCount); 3 // [p retain]; 4 Person * q = [p retain]; 5 NSLog(@"%lu",q.retainCount); 6 7 [p release]; 8 NSLog(@"%lu",p.retainCount); 9 10 [q release]; 11 NSLog(@"%lu",p.retainCount); 12 [p run];
创建一个Person类 一个p指针指向创建的对象 创建完成之后 计数器为1 创建一个q指针指向p原本指的对象 计数器+1为2
[p release] 计数器-1 [q release] 计数器再-1 此时对象释放
1 - (void)dealloc { 2 NSLog(@"人被释放了"); 3 [super dealloc]; 4 }
类中重写dealloc方法 则会输出 人被释放了 若开启僵尸对象检测 则[p run]报错
// 为什么最后不为0?
最后一次输出,引用计数没有变成0.
这是为什么呢?
因为该对象的内存已经被回收,而我们向一个已经被回收的对象发了一个retainCount消息,所以它的输出结果应该是不确定的,如果该对象所占的内存被复用了,那么就有可能造成程序异常崩溃。
那为什么在这个对象被回收之后,这个不确定的值是1而不是0呢?这是因为当最后一次执行release时,系统知道马上就要回收内存了,就没有必要再将retainCount减1了,因为不管减不减1,该对象都肯定会被回收,而对象被回收后,它的所有的内存区域,包括retainCount值也变得没有意义。不将这个值从1变成0,可以减少一次内存操作,加速对象的回收。
2.单个对象的内存管理
2.1.1 内存泄露的第一种情况
1 Person * p = [[Person alloc] init]; 2 NSLog(@"%lu",p.retainCount); 3 Person * q = [p retain]; 4 NSLog(@"%lu",q.retainCount); 5 [p release];//引用两次需要两次release 少写一次都会内存泄露 6 [q release];
2.1.2内存泄露的第二种情况
1 Person * p2 = [[Person alloc] init]; 2 NSLog(@"%lu",p2.retainCount); 3 p2 = nil; 4 [p2 release]; // [nil release]
释放前指向nil 然后release 之前的对象没有被释放
2.2.1 野指针的提前释放
1 Person * p = [[Person alloc] init]; 2 NSLog(@"%lu",p.retainCount); 3 Person * q = [p retain]; 4 5 [p release]; 6 // p = nil; 7 [q release]; 8 //q = nil; 9 [p run]; // [nil run] // nil 调用任何方法都不会报错
提前释放之后再用对象的方法 会形成提前释放的野指针
2.2.2野指针的重复释放
1 Person * p = [[Person alloc] init]; 2 NSLog(@"%lu",p.retainCount); 3 [p release]; 4 [p release]; 5 //重复释放
释放之后 不可retain 无法起死回生
3.多个对象的内存管理
1 // 创建Person对象 2 Person * p = [[Person alloc] init]; 3 // 创建Car对象 4 Car * c = [[Car alloc] init]; 5 // 让人有一辆车 6 [p setCar:c]; 7 [p drive]; 8 [c release]; 9 [p drive]; 10 // 只要p对象存在,就可以随意调用自己的方法 11 [p release];
1 //Car类成员方法的实现以及dealloc的重写 2 - (void)dealloc { 3 NSLog(@"dealloc 车被销毁了"); 4 [super dealloc]; 5 } 6 7 - (void)run { 8 NSLog(@"车跑起来了"); 9 }
1 //Person类成员方法的实现以及dealloc方法的重写 2 3 - (void)setCar:(Car *)car { 4 //在setCar的时候,让car的retainCount+1 5 // 目的是保证p对象存在的时候,_car对象一定存在 6 _car = [car retain]; 7 } 8 9 - (Car *)car { 10 return _car; 11 } 12 13 - (void)drive { 14 [_car run]; 15 } 16 17 - (void)dealloc { 18 NSLog(@"dealloc 人被销毁了"); 19 // 保证p对象销毁的时候,他所持有的_car对象也被销毁,防止出现内存泄露 20 [_car release]; 21 [super dealloc]; 22 }
set方法的内存管理
若两次set方法的参数都为同一对象 那么会先把对象release一次之后再retain 但release之后就释放掉了 所以要判断参数是否和对象的成员相等
1 - (void)setCar:(Car *)car { 2 //在setCar的时候,让car的retainCount+1 3 // 目的是保证p对象存在的时候,_car对象一定存在 4 5 // 如果调用 _car = car,并且_car的retainCount=1 的情况下会出现野指针问题 6 if (_car != car) { 7 // 第一次运行 [nil release] 8 [_car release]; 9 _car = [car retain]; 10 } 11 12 }
set方法可以改写为如下所示。
[email protected]的参数
// 1与内存管理相关的参数
// 默认是 assign
// retain 生成符合内存管理原则的set方法
// assign 直接赋值,不考虑内存管理(一般基本数据类型)
// 2 与多线程相关的代码
// notatomic 不生成与多线程相关的代码 默认 iOS开发中用这个
// atomic 生成与多线程相关的代码 mac开发中会用到
// 3 是否生成set与get方法
// readonly 只生成get方法
// readwrite 生成get与set方法 默认
// 没有只生成set方法的参数
// 4 set与get方法名称相关的参数
// setter 设置set方法的名字(有冒号);
// getter 设置get方法的名字
5.循环引用问题
Person类有Car类的属性
Car类也有Person类的属性 就成为循环引用
.h文件中导入头文件应换成 @class 类名 的形式
前向声明 告诉系统有这样一个类
.m文件中应导入头文件 调用类的成员方法
引用关系应该一强一弱
1 #import <Foundation/Foundation.h> 2 #import "Person.h" 3 #import "Car.h" 4 int main(int argc, const char * argv[]) { 5 @autoreleasepool { 6 // p 1 car 1 7 Person * p = [[Person alloc] init]; 8 Car * car = [[Car alloc] init]; 9 // p 1 car 2 10 [p setCar:car]; 11 // car 2 p 1 12 [car setPerson:p]; 13 // p 0 car 1 14 [p release]; //难点总结:p release之后 计数count 变为0,此时调用 p 的 dealloc 函数,随之成员变量_car release 15 // p 0 car 0 16 17 [car release]; 18 19 20 21 22 // // p 1 car 1 23 // Person * p = [[Person alloc] init]; 24 // Car * car = [[Car alloc] init]; 25 // // p 1 car 1 26 // [p setCar:car]; 27 // // car 1 p 2 28 // [car setPerson:p]; 29 // // p 1 car 1 30 // [p release]; 31 // // p 0 car 0 32 // [car release]; 33 34 35 36 37 // // p 1 car 1 38 // Person * p = [[Person alloc] init]; 39 // Car * car = [[Car alloc] init]; 40 // // p 1 car 2 41 // [p setCar:car]; 42 // // car 2 p 2 43 // [car setPerson:p]; 44 // // p 1 car 2 45 // [p release]; 46 // // p 1 car 1 47 // [car release]; 48 49 } 50 return 0; 51 }
main.m
1 #import <Foundation/Foundation.h> 2 @class Car; // 前向声明 告诉系统有这样一个类 3 @interface Person : NSObject 4 @property (nonatomic,retain) Car * car; 5 @end
Person.h
1 #import "Person.h" 2 #import "Car.h" 3 @implementation Person 4 - (void)dealloc { 5 NSLog(@"person 被销毁了"); 6 [_car release]; 7 [super dealloc]; 8 } 9 @end
Person.m
1 #import <Foundation/Foundation.h> 2 @class Person; 3 @interface Car : NSObject 4 @property (nonatomic,assign) Person * person; 5 @end
Car.h
1 #import "Car.h" 2 #import "Person.h" 3 @implementation Car 4 - (void)dealloc { 5 NSLog(@"car 被销毁了"); 6 // [_person release]; 弱引用 所以没有这行 7 [super dealloc]; 8 } 9 @end
Car.m
未完待续。。。。。