一、内存管理的意义
移动设备的内存比较有限,每个app所能占用的内存也是有限制的,超出限制范围可能导致程序崩溃,需要合理分配内存。内存管理的对象是任何继承了NSObject的对象,对其他基本数据类型(int、double、float、char、struct、enum等)无效。这是由于对象存储在堆空间,基本数据类型存储在栈空间,栈空间中的变量系统会自动回收,不需要进行内存管理。
二、引用计数器
2.1 每个OC对象都有自己的引用计数器,它是一个整数变量,占4个字节的存储空间,表示“对象被引用的次数”。
2.2 当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1。
2.3 当一个对象的引用计数器值为0时,对象占用的内存就会被系统回收。反之,如果不为0,则它占用的内存就不可能被回收,除非整个程序退出。
2.4 引用计数器的操作
1> 给对象发送一条retain消息,可以使引用计数器+1(retain方法返回对象本身)
2> 给对象发送一条release消息,可以使引用计数器-1。(没有返回值)
3> 给对象发送一条retainCount消息,可以获得当前的引用计数器值
三、对象的销毁—— - dealloc
3.1 当对象的引用计数器值为0时,对象将被销毁,其所占用的内存被系统回收,同时,系统会自动向对象发送一条dealloc消息。
3.2 当需要释放引用的相关资源时,一般会重写dealloc方法,在里面进行操作,dealloc就像对象的遗言。一旦重写了dealloc方法,就必须在方法最后面调用[super dealloc]。
3.3 一旦对象被回收了,它占用的内存就不可再用,坚持使用会导致程序崩溃(野指针错误)。
3.4 重写-dealloc可以做为判断对象是否存在的依据,当对象销毁时,会调用-dealloc。如果调用到的话,则对象将被销毁。
四、取消ARC的三种方式
4.1 要想手动调用retain、release等方法,在创建项目的时候不要勾选ARC。在6.1以下(不包括6.1)的版本中,新建项目选项那里不要勾选Use Automatic Reference Counting的复选框。
4.2 对整个项目关闭ARC
project -> Build settings -> Apple LLVM complier 3.0 - Language -> objective-C Automatic Reference Counting设置为NO
4.3 对某个文件单独设置
当有的时候某些文件还是会报release之类的错,这时需要对某个文件单独设置。
1> project-Build Phases-Compile Sources
2> 找到需要设置的mm文件,在右边Compiler Flag里把-fobjc-arc改成 -fno-objc-arc
3> Clean-Build。一切OK!
五、僵尸对象:所占用内存已经被回收的对象
5.1 当对象销毁后,原先占用的内存不可用,即变成僵尸对象。
5.2 僵尸对象不能再使用,无法对一个僵尸对象发送retain消息。
5.3 开启僵尸对象的检测:
六、野指针
6.1 指向僵尸对象(不可用内存)的指针就是野指针。
6.2 为了避免野指针的存在,可以在释放对象后将指针设置成空指针(p = nil)。OC中不存在空指针错误,给空指针发送消息,不报错。
6.3 给野指针发送消息会报错(EXC_BAD_ACCESS)
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property int age;
@end
@implementation Person
// 当一个Person对象被回收的时候,就会自动调用dealloc方法
- (void)dealloc
{
NSLog(@"Person对象被回收");
// super的dealloc一定要调用,而且放在最后面
[super dealloc];
}
@end
int main() {
Person *p = [[Person alloc] init]; //调用alloc方法,计数器值+1。计数器值:1
NSUInteger count = [p retainCount];//返回1
NSLog(@"计数器:%ld", count);
[p retain]; // 调用retain方法,计数器值+1,并返回对象本身。计数器值:2
[p release]; // 调用release方法,计数器值-1。计数器值:1
[p release]; // 计数器值:0.销毁对象
//调用p.age=10:message sent to deallocated instance 0x100100340
//给已经释放的对象发送一条-setAge:消息
//p.age=10;
//[p retain]; // 无法对一个已经销毁的对象调用retain方法
p = nil; // 将指针变成空指针
// EXC_BAD_ACCESS:访问了一块坏的内存(已经被回收,已经不可用的内存)
// 野指针错误
// OC不存在空指针,给空指针发送消息,不报错。
//[p release];
return 0;
}
七、多对象间的内存管理原则
7.1 原则分析
1>只要还有人在用某个对象,那么这个对象就不会被回收
2> 只要你想用这个对象,就让对象的计数器+1
3> 当你不再使用这个对象时,就让对象的计数器-1
7.2 谁创建,谁release
如果通过alloc、new或[mutable]copy来创建一个对象,那么就必须调用release或autorelease。换句话说,不是你创建的,就不用你去[auto]release。
7.3 谁retain,谁release
只要你调用了retain,无论这个对象是如何生成的,你都要调用release
7.4 总结
1>有始有终,有加就有减
2>曾经让对象的计数器+1,就必须在最后让对象计数器-1
8.set方法的代码规范
8.1 基本数据类型:基本数据类型由于不需要进行内存管理,直接复制就可以了
- (void)setAge:(int)age
{
_age=age;
}
8.2 OC对象类型
- (void)setCar:(Car *)car
{
//1.先判断是不是新传进来的对象
if(car != _car)
{
//2.对旧对象做一次release
[_car release];
//3.对新对象做一次retain
_car = [car retain];
}
}
9.dealloc方法的代码规范
9.1 一定要调用[super dealloc],而且放到最后面
9.2 对self(当前)所拥有的其他对象做一次realease
- (void)dealloc
{
[_car release];
[super dealloc];
}
10 @property参数
10.1 由于单纯的使用@property自动生成的setter方法只是在方法内部简单的赋值,因此当属性是继承自NSObject的一个对象时,在setter方法内部没有进行相应的retain和release处理,将会导致对象在使用完成后没有被系统回收,造成内存泄漏。因此,需要和参数进行配合使用。
10.2 控制set方法内存管理的相关参数
1> retain : release旧值,retain新值(适用于OC对象类型)。编译器在解析到retain参数时(如 @property (retain) Car car;),会将set方法自动解析成:
- (void)setCar:(Car *)car
{
if(car != _car){
[_car release];// 对旧对象做一次release
_car = [car retain];// 对新对象做一次retain
}
}
2> assign :直接赋值(默认,适用于非OC对象)
3> copy : release旧值,copy新值(一般用于NSString *)
10.3 控制是否要生成set方法
1> readwrite : 同时生成set方法和get方法的声明和实现(默认)
2> readonly : 只生成get方法的声明和实现
10.4 多线程管理 : nonatomic和atomic,决定编译器生成的set方法和get方法是否为原子操作。
1> nonatomic :性能高(一般就用这个)。
使用nonatomic参数,get方法只是简单的返回值。
2> atomic : 性能低(默认)。
atomic是OC使用的一种线程保护技术。防止在写未完成的时候被另外一个线程读取,造成数据错误。而这种机制是耗费系统资源的,所以在iPhone这种小型设备上,如果没有使用多线程间的通讯编程,那么nonatomic是一个非常好的选择。
加了atomic,setter函数会变成下面这样:
{lock}
if (property != newValue) {
[property release];
property = [newValue retain];
}
{unlock}
10.5 控制set方法和get方法的名称
1. setter: 设置set方法的名称,方法名后一定要有个冒号:。(很少用)
@property (setter =abc:) int age;
2. getter: 设置get方法的名称(一般用在BOOL类型)
返回BOOL类型的方法名一般是以is开头:
@property (getter =isRice) BOOL rich;
3.不管getter把get方法名改成什么名字,点语法都可以通过原来的方法名或者更改后的方法名获得字段值。即通过p.isRice和p.rich在获取值时是等价的,编译器内部会做转换。
10.6 注意
在同一个方法列表中,以上10.2-10.5每一个方法类型里面最多只能使用一个(可以不使用),如果使用多个的话会冲突。
如: @property (nonatomic, assign, readwrite) int age;
@property (nonatomic, assign) int Number;
@property (retain) NSString *name;
//@property (retain, assign) int Number; // 这是错误的,因为retain和assign是同一种方法类型,都是操作set方法内存管理的。
// @property (readwrite, readonly) int age; // 这是错误的,因为readwrite是控制生成get方法和set方法,readonly控制只生成get方法,相互冲突了。
11. 多对象的循环引用
11.1 开发中引用一个类的规范
1> 在.h文件中用@class 声明类。
2> 在.m文件中用#import来包含类的所有东西。
11.2 @class的作用
仅仅告诉编译器,某个名称是一个类名。如@class Person;仅仅是告诉编译器,Person是一个类。
11.3 两端循环引用解决方案
1> 一端用retain
2> 一端用assign
12、autorelease和@autoreleasepool
12.1 autorelease的基本用法
1> 会将对象放到一个自动释放池中
2> 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
3> 会返回对象本身
4> 调用完autorelease方法后,对象的计数器不变
12.2 autorelease的好处
1> 不用再关心对象释放的时间
2> 不用再关心什么时候调用release
12.3 autorelease的使用注意
1> 占用内存较大的对象不要随便使用autorelease
2> 占用内存较小的对象使用autorelease,没有太大影响
3> alloc之后调用了autorelease,不能再调用release,否则会发生野指针错误
4> 不能连续调用多次autorelease。如Person *p = [[[[Person alloc] init] autorelease] autorelease];
12.4 自动释放池:@autoreleasepool
1> 自动释放池可以创建无限多个。在IOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构存在(先进后出)。
2> 当一个对象调用autorelease方法时,会将对象放到栈顶的自动释放池里,并返回对象本身
3> 自动释放池本身也是一个对象,当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
12.5 autorelease的应用
1> 系统自带的方法里面没有包含alloc、new、copy,说明返回的对象都是autorelease的
2> 开发中经常会提供一些类方法,快速创建一个已经autorelease过的对象。创建对象时不要直接用类名,一般用self。
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic, assign) int age;
+(id)person;
+(id)personWithAge:(int)age;
@end
@implementation Person
//快速创建一个已经autorelease过的对象
+ (id)person
{
//创建对象时不要直接用类名,一般用self
return [[[self alloc] init] autorelease];
}
//快速创建一个已经autorelease过的带参数的对象
+(id)personWithAge:(int)age
{
Person *p = [self person];
p.age = age;
return p;
}
-(void)dealloc
{
NSLog(@"%d岁的人被销毁了", _age;);
}
@end
int main()
{
@autoreleasepool
{
Person *p = [Person personWithAge : 100];
}
}
13 ARC
13.1 ARC是一种编译器特性,当编译器解析到alloc、new时,会自动在合适的地方加入release代码。不允许调用release、retain、retainCount,允许重写dealloc,但不允许调用[super dealloc]。
13.2 ARC的判断准则:只要没有强指针指向对象,就会释放对象
1> strong : 成员变量是强指针(适用于OC对象类型)。非ARC下的的retain改用为strong。
2> weak : 成员变量是弱指针(适用于OC对象类型)。主要用于ARC环境下两个对象的循环引用,可以一端用strong,一端用weak解决。
3> assign : 适用于非OC对象类型
13.3 指针类型
1> 强指针:默认情况下,所有的指针都是强指针。在ARC的情况下,针对继承自NSObject的对象,@property参数不用retain,而是用strong。如:@property (nonatomic,strong) Dog *dog;
2> 弱指针:__weak
13.4 非ARC项目转换为ARC
Edit->Refactor->Convert to Objective-C ARC
13.5 单文件的ARC控制:项目文件->Build Phases->Compile Sources中输入以下参数控制
-fno-objc-arc
-f-objc-arc
14. 当两端循环引用的时候,解决方案:
14.1 ARC:一端用strong,另一端用weak
14.2 非ARC: 一端用retain,另一端用assign