内存管理知识点总结
一、内存区域分类:
1、堆区:需要的时候系统会为你分配内存,但是系统不会自动回收,需要程序员手动释放内存
2、栈区:需要的时候系统会为你分配内存,不需要的时候系统自动回收该内存
3、常量区:存储常量,数据不能修改
4、全局,静态区:存储全局变量和静态变量
5、自由存储区
二、注意:
1、内存管理只针对继承NSObject的对象,对其他基本数据类型无效(int double NSRange等)
2、mac OS 有垃圾回收机制,但iOS没有,其内存管理需要开发者处理
3、内存管理的目的:保证每个对象在使用的时候存在内存中,不用的对象在最后从内存中清除
4、悬垂指针:如果在最后打印对象的retainCount应该为0,但因为悬垂指针结果会是1,所以retainCount仅仅作为参考,不能进行逻辑判断,可以在dealloc函数中添加一句输出语句来验证是否对象已经不存在
5、常量不需要内存管理,如果打印其retainCount为正无穷
6、要获取一个对象的持有权,引用计数必须加1,所以在初始化时要注意,不能用常规的,在MRC状态下,多个对象内存管理需要如下操作:
1 - (void)setBook:(Book *)book { 2 3 // 比较两个是否是同一个对象,所以用 != 4 if (_book != book) { // 判断旧的和新的是否是同一个,如果是同一个,不需要释放,不操作 5 6 [_book release]; // 当重新赋值的时候,走set方法,把之前的联系解除(释放旧的) 7 8 _book = [book retain]; // 保证用户持有该对象 ---- 引用计数必须加1(持有新的) 9 10 } 11 } 12 13 14 // ----------------- 自定义初始化的方法 (不需要release,在dealloc里面释放) -------------- 15 - (instancetype)initWithBook:(Book *)book andName:(NSString *)name { 16 self = [super init]; 17 if (self) { 18 _book = [book retain]; // 保证持有 19 20 _name = [name retain];; 21 } 22 return self; 23 } 24 25 26 #pragma mark - 便利构造 27 + (Person *)personWithBook:(Book *)book andName:(NSString *)name { 28 Person *per = [[Person alloc]initWithBook:book andName:name]; 29 30 return [per autorelease]; // 不能直接用release,因为那样无法返回 31 }
7、在类的实现文件中,必须包含dealloc方法:
// 当retainCount为0时,自动走该方法,无需自己调用 可以根据程序是否走该方法判断对象是否释放
- (void)dealloc {
NSLog(@"person dealloc");
[_book release]; // 在哪里持有,哪里释放,所以当person没有的时候,释放其持有
// 上面也可以写成_book = nil; 效果相同
[_name release]; // 属性 必须在dealloc中释放å
[super dealloc];
}
8、集合中的内存管理
某个对象加入到数组中,其retainCount + 1,不影响数组的retainCount
数组的retainCount改变,不影响数组里面的元素的retainCount
数组对象被销毁时,里面的对象元素,retainCount - 1 注意不是清零 示例代码如下:
Person *per = [[Person alloc]init];
NSArray *array = [[NSArray alloc]initWithObjects:@"1", @"2", per, nil];
NSLog(@"per = %lu, arr = %lu", per.retainCount, array.retainCount);
[array retain];
NSLog(@"per = %lu, arr = %lu", per.retainCount, array.retainCount);
[array release];
[array release];// 数组销毁的时候会使per的retainCount -1 ,所以下面只需要一次release
[per release];
三、自动释放池
如果对象是在自动释放池中,则不需要release,因为在程序结束的时候,自动释放池会自己清除其里面的内容
1 // NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init]; 2 // [pool release]; 被封装 3 4 // 当pool release的时候,所有加autorelease的retainCount - 1 5 6 Person *per = [[Person alloc]init]; 7 [per retain]; 8 [per autorelease]; 9 [per autorelease]; 10 11 NSLog(@"per = %lu", per.retainCount); 12 13 NSArray *array = [[[NSArray alloc]initWithObjects:@"1", nil]autorelease]; 14 // 等价于上面alloc + autorelease, 系统的便利构造方法不需要内存管理 15 16 NSArray *array1 = [NSArray arrayWithObjects:@"2", nil];
四、循环引用:让其中一个引用变为弱引用(assign),在相互导入的时候,一个用@class,并在.m文件中用#import导入 示例如下:
1 #import <Foundation/Foundation.h>
2
3 @class Person; // 互相引用的时候,防止循环引用 不能再.m文件中访问其属性和方法,所以要在.m 中import Person.h
4
5 @interface Book : NSObject
6
7 @property (nonatomic,assign) Person *per; // 不会让引用计数加1,弱引用,打破平衡,让其中一个先减为0,才会进入dealloc
8
9
10 @end
四、其他错误总结:
ARC:
1. retain(copy) -- strong
2. weak 修饰不需要持有的对象类型
MRC:
不需要持有对象:assign
需要持有对象:retain copy
1 NSString *str = @"cassie";
2
3 NSLog(@"常量:%lu", str.retainCount);
4
5 NSString *str1 = [[NSString alloc]initWithFormat:@"你好"];
6
7 [str1 release]; // 中文 -- 存在堆区,需要release
8
9 NSLog(@"中文str:%lu", str1.retainCount);
10
11 // 所以在用NSString类型的对象时,用release,防止内存泄露
谁持有谁释放(遵循原则) 错误示例及解释如下:
NSArray *array = @[@"1"]; 相当于遍历构造 不需要释放
1、混乱释放
Person * person = [[Person alloc] init];
Person * person1 = person; person1 只是一个指针
[person1 release];// 不遵守内存管理原则,不持有person1,应该释放person
2、内存泄露
Person * person1 = [[Person alloc] init];
Person * person2 = [[Person alloc] init];
person2 = person1;//指针指向发生改变,person2原有内存 泄露, 解决方案 autorelease
3、过度释放
Person * person = [Person personWithName:@"tom" age:12];
[person release];//便利构造器初始化的对象,过度释放了,不需要release
4、nil对象的引用计数为0
Person * person = nil;
NSLog(@"%lu", [person retainCount]);
5、常量对象的引用计数为无穷
NSString * name = @"name";
NSLog(@"%lu", [name retainCount]);
常量型(英文 -- 在常量区)的字符串无需内存管理 写release没有影响