文章目录
预备知识
- 任何继承了NSObject的对象都需要进行内存管理,其他非对象类型(int、char、float、double、struct、enum等不需要进行内存管理
- 继承了NSObject的对象存储在操作系统的堆里,堆一般由程序员分配释放,程序结束时可能有OS回收。
- 非OC对象一般放在操作系统的栈里,栈由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈(先进后出)
五大内存区域:栈区、堆区、全局区、常量区、代码区
- 内存管理模型
- 自动垃圾收集
- 手动引用计数 MRC
- 自动引用计数 ARC
在项目中打开MRC
手动引用计数MRC
- 引用计数器,可以理解为对象被引用的次数,每一个对象都有自己的引用计数器,引用计数为0时,对象占用的内存会被回收
内存管理的思考方式
- 自己生成的对象,自己持有
- 非自己生成的对象,自己也能持有
- 不再需要自己持有的对象时释放
- 非自己持有的对象无法释放
自己生成的对象,自己持有
使用alloc、new、copy、mutableCopy开头的方法意味着自己生成的对象只有自己持有,不需要retain
//自己生成并持有对象
id obj1 = [[NSObject alloc] init];
id obj2 = [[NSObject new];
非自己生成的对象,自己也能持有
用 alloc / new / copy / mutableCopy 以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者。
例如NSMutableArray类的array类方法。
id obj = [[NSMutableArray array];
[obj retain];
[NSMutableArray array] 相当于 [[[NSMutableArray alloc] init] autorelease];
使用autorelease方法,可以使取得的对象存在,但自己不持有对象。通过retain操作持有对象。
不再需要自己持有的对象时释放
id obj = [[NSObject alloc] init];
//释放对象
[obj release];
无法释放非自己持有的对象
//这一部分存在疑惑
id obj = [[NSObject alloc] init];
[obj release];
[obj release];
NSLog(@"%p", obj);
- 这段代码在执行NSLog时可能会报错,因为对象所占的内存在“解除分配”之后,只是放回了“可用内存池”。如果执行NSLog时尚未覆写对象内存,那么该对象仍然有效,这时程序不会崩溃,所以调试过程中可能结果不一样,为避免在不经意间使用了无效对象,一般调完release之后都会清空指针。这就能保证不会出现可能指向无效对象的指针,这种指针通常称为“悬挂指针”。
[obj release];
obj = nil;
-
这部分存在疑惑的是,对obj进行第二次release操作时,应该会导致崩溃,但是并没有…
-
(补充)对于对象的内存回收的理解:当对象的被回收时,其所占有的内存空间就可以分配给别人,在没有分配给别人之前,对象的数据还存在。
@property参数
- 在成员变量前加上@property,系统就会自动帮我们生成基本的setter/getter方法
@property (nonatomic) int val;
- 如果在property后边加上retain,系统就会自动帮我们生成getter/setter方法内存管理的代码,但是仍需要我们自己重写dealloc方法
@property(nonatomic, retain) Room *room;
- 如果在property后边加上assign,系统就不会帮我们生成set方法内存管理的代码,仅仅只会生成普通的getter/setter方法,默认什么都不写就是assign
@property(nonatomic, retain) int val;
MRC中避免循环retain
//Person.h
#import <Foundation/Foundation.h>
@class Son;
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, retain) Son *son;
@end
//Son.h
#import <Foundation/Foundation.h>
@class Person;
NS_ASSUME_NONNULL_BEGIN
@interface Son : NSObject
@property (nonatomic, retain) Person *p;
@end
//main.m
Son *son = [[Son alloc] init];
Person *person = [[Person alloc] init];
son.p = person;
person.son = son;
[person release];
[son release];
[son release];
会造成内存泄漏,堆中的两个对象的内存回收不了
解决办法:
- 一端用retain,一端用assign
//Person.h
@property (nonatomic, retain) Son *son;
//Son.h
@property (nonatomic, assign) Person *p;
retainCount
NSObject协议中定义了下列方法,用于查询对象当前的引用计数
- (NSUInteger)retainCount;
ARC中已将此方法废弃
保留计数的绝对数值一般都与开发者所应留意的事情完全无关,即便在调试时会用到此方法,通常也还是无所助益的。
此方法之所以无用,首要原因在于:他所返回的保留计数只是某个给定时间点上的值。并未考虑到对象是否处于自动释放池中稍后系统会将自动释放池情况这种情况。
举个例子
while ([object retainCount]) {
[object release];
}
有两个错误
- 并未考虑到后续自动释放操作,只是不停通过释放操作降低保留计数,直至保留计数为0被系统回收,并未考虑到对象是否在自动释放池中。如果对象在自动释放池中,在系统清空池子时,会对对象再做一次release操作,这时就会导致程序崩溃。
- retainCount可能永远不会返回0,因为有时系统会优化对象的释放行为,在保留计数还是1的时候就把它回收了。只有在系统不打算这么优化时,计数值才会递减至0。所以,这段代码可以正常运行,多半是运气。
在我们希望系统回收对象时,应确保没有尚未抵消的retain保留操作,避免内存泄漏。
保留计数值过大原因
打印结果:
前三个对象皆为“单例对象”,所以其保留计数都很大。
- 系统会尽可能把NSString实现成单例对象。如果字符串像例子上的那样,是一个编译期常量,那么就可以这样来实现了。在这种情况下,编译器会把NSString对象所表示的数据放在应用程序的二进制文件里,这样的话,运行程序时就可以直接用了,无需再创建NSString对象。
- NSNumber也类似,它使用了一种叫做“标签指针”的概念来标注指定类型的数值。这种做法不使用NSNumber对象,而是把与数值有关的全部消息都放在指针值里。运行期系统会在消息派发期间检测到这种标签指针,并对它执行相应操作,使其行为看上去和真正的NSNumber对象一样。这种优化只在某些场合使用,例子中的浮点数对象就没有优化,所以保留计数为1
对于上述所说的单例对象,其保留计数绝对不会改变。这种对象的保留及释放操作都是“空操作”。
打印结果:
2.0 36条中明确指出:不要使用retainCount
我们在MRC中,有时可能会想要打印引用计数,但retainCount方法并不是很有用,由于对象可能会处于自动释放池中,这会导致打印的引用计数并不精准,而且其他程序库也很有可能自行保留或释放对象,这都会扰乱引用计数的具体值。
autorelease
顾名思义,autorelease就是自动释放。
类似于C语言的局部变量,超出变量作用域,局部变量就会被废弃,不可再访问。
autorelease会在超出作用域时,调用对象的release实例方法,与C语言不同的是,编程人员可以设定变量的作用域
autorelease方法会返回对象本身,且调用完autorelease方法后,对象的计数器不变。autorelease实际上只是把对release的调用延迟了,对于每一个autorelease,系统只是把该对象放入了当前的autorelease pool中,当该pool被释放时,该pool中的所有对象会被调用release。
autorelease的具体使用方法如下:
- 生成并持有NSAutoreleasePool对象
- 调用已分配对象的autorelease实例方法
- 废弃NSAutoreleasePool对象
对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。
内容不完整,后续补充