一、 基本原理
1. 什么是内存管理
Ø 移动设备的内存极其有限,每个app所能占用的内存是有限制的
Ø 当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间。比如回收一些不需要使用的对象、变量等
Ø 管理范围:任何继承了NSObject的对象,对其他基本数据类型(int、char、float、double、struct、enum等)无效
2. 对象的基本结构
Ø 每个OC对象都有自己的引用计数器,是一个整数,表示“对象被引用的次数”,即有多少人正在使用这个OC对象
Ø 每个OC对象内部专门有4个字节的存储空间来存储引用计数器
3. 引用计数器的作用
Ø 当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1
Ø 当一个对象的引用计数器值为0时,对象占用的内存就会被系统回收。换句话说,如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收,除非整个程序已经退出
4. 引用计数器的操作
Ø 给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身)
Ø 给对象发送一条release消息,可以使引用计数器值-1
Ø 可以给对象发送retainCount消息获得当前的引用计数器值
5. 对象的销毁
Ø 当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收
Ø 当一个对象被销毁时,系统会自动向对象发送一条dealloc消息
Ø 一般会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言
Ø 一旦重写了dealloc方法,就必须调用[superdealloc],并且放在最后面调用
Ø 不要直接调用dealloc方法
Ø 一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)
示例代码:
//
// main.m
// 01-引用计数器的基本操作
//
// Created by apple on 13-8-9.
// Copyright (c) 2013年 itcast. All rights reserved.
//
/*
1.方法的基本使用
1> retain :计数器+1,会返回对象本身
2> release :计数器-1,没有返回值
3> retainCount :获取当前的计数器
4> dealloc
* 当一个对象要被回收的时候,就会调用
* 一定要调用[super dealloc],这句调用要放在最后面
2.概念
1> 僵尸对象 :所占用内存已经被回收的对象,僵尸对象不能再使用
2> 野指针 :指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错(EXC_BAD_ACCESS)
3> 空指针 :没有指向任何东西的指针(存储的东西是nil、NULL、0),给空指针发送消息不会报错
*/
#import <Foundation/Foundation.h>
#import "Person.h"
int main()
{
// 1
Person *p = [[Person alloc] init];
//NSUInteger c = [p retainCount];
//NSLog(@"计数器:%ld", c);
// 2 retain方法返回的是对象本身
[p retain];
// 1
[p release];
// 0 野指针:指向僵尸对象(不可用内存)的指针
[p release];
[p retain];
// -[Person setAge:]: message sent to deallocated instance 0x100109a10
// 给已经释放的对象发送了一条-setAge:消息:
p.age = 10;
//[p setAge:10];
// 指针p变成空指针
//p = nil;
// EXC_BAD_ACCESS : 访问了一块坏的内存(已经被回收、已经不可用的内存
// 野指针错误
// OC不存在空指针错误,给空指针发送消息,不报错
[p release];
[p release];
[p release];
[p release];
[nil release];
return 0;
}
二、 Xcode的设置
1. 取消ARC
要想手动调用retain、release等方法,在创建项目的之后关闭ARC
xcode5.1.1中设置方法如下:
2. 开启僵尸对象监控
默认情况下,Xcode是不会管僵尸对象的,使用一块被释放的内存也不会报错。为了方便调试,应该开启僵尸对象监控
三、 内存管理原则
1. 原则分析
1.你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作)
2.你不想再使用(占用)某个对象,就应该让对象的计数器-1(让对象做一次release)
3.谁retain,谁release
4.谁alloc,谁release
2. 谁创建,谁release
Ø 如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease
Ø 换句话说,不是你创建的,就不用你去[auto]release
3. 谁retain,谁release
Ø 只要你调用了retain,无论这个对象是如何生成的,你都要调用release
4. 总结
Ø 有始有终,有加就有减
Ø 曾经让对象的计数器+1,就必须在最后让对象计数器-1
四、 set方法的内存管理
内存管理代码规范:
1.只要调用了alloc,必须有release(autorelease)
对象不是通过alloc产生的,就不需要release
2.set方法的代码规范
1> 基本数据类型:直接复制
- (void)setAge:(int)age
{
_age = age;
}
2> OC对象类型
- (void)setCar:(Car *)car
{
// 1.先判断是不是新传进来对象
if ( car != _car )
{
// 2.对旧对象做一次release
[_car release];
// 3.对新对象做一次retain
_car = [car retain];
}
}
3.dealloc方法的代码规范
1> 一定要[super dealloc],而且放到最后面
2> 对self(当前)所拥有的其他对象做一次release
- (void)dealloc
{
[_car release];
[super dealloc];
}
五、 @property参数
1. 控制set方法的内存管理
Ø retain: release旧值,retain新值(用于OC对象)
Ø assign:直接赋值,不做任何内存管理(默认,用于非OC对象类型)
Ø copy :release旧值,copy新值(一般用于NSString *)
2. 控制需不需生成set方法
Ø readwrite:同时生成set方法和get方法(默认)
Ø readonly :只会生成get方法
3. 多线程管理
Ø atomic :性能低(默认)
Ø nonatomic:性能高
4. 控制set方法和get方法的名称
Ø setter:设置set方法的名称,一定有个冒号:
Ø getter:设置get方法的名称
示例代码:
person.h文件如下:
/*
作者:MJ
描述:
时间:
文件名:Person.h
*/
#import <Foundation/Foundation.h>
#import "Book.h"
@interface Person : NSObject
@property int age;
// retain : 生成的set方法里面,release旧值,retain新值
@property (retain) Book *book;
@property (retain) NSString *name;
@end
对应person.m文件中
/*
作者:MJ
描述:
时间:
文件名:Person.m
*/
#import "Person.h"
@implementation Person
//- (void)setBook:(Book *)book
//{
// if (_book != book)
// {
// [_book release];
//
// _book = [book retain];
// }
//}
- (void)dealloc
{
[_book release];
[_name release];
[super dealloc];
}
@end
最后property参数总结
<pre name="code" class="objc">/*
作者:MJ
描述:
时间:
文件名:Person.h
*/
#import <Foundation/Foundation.h>
/*
1.set方法内存管理相关的参数
* retain : release旧值,retain新值(适用于OC对象类型)
* assign : 直接赋值(默认,适用于非OC对象类型)
* copy : release旧值,copy新值
2.是否要生成set方法
* readwrite : 同时生成setter和getter的声明、实现(默认)
* readonly : 只会生成getter的声明、实现
3.多线程管理
* nonatomic : 性能高 (一般就用这个)
* atomic : 性能低(默认)
4.setter和getter方法的名称
* setter : 决定了set方法的名称,一定要有个冒号 :
* getter : 决定了get方法的名称(一般用在BOOL类型)
*/
@interface Person : NSObject
// 返回BOOL类型的方法名一般以is开头
@property (getter = isRich) BOOL rich;
//
@property (nonatomic, assign, readwrite) int weight;
// setWeight:
// weight
//
@property (readwrite, assign) int height;
@property (nonatomic, assign) int age;
@property (retain) NSString *name;
@end
六、 循环引用
1. @class
Ø 使用场景
对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类
这种代码编译会报错。当使用@class在两个类相互声明,就不会出现编译报错
1.@class的作用:仅仅告诉编译器,某个名称是一个类
@class Person; // 仅仅告诉编译器,Person是一个类
2.开发中引用一个类的规范
1> 在.h文件中用@class来声明类
2> 在.m文件中用#import来包含类的所有东西
2. 循环retain
Ø 比如A对象retain了B对象,B对象retain了A对象
Ø 这样会导致A对象和B对象永远无法释放
3. 解决方案
Ø 当两端互相引用时,应该一端用retain、一端用assign
注:当开启arc时 一端用strong ,一端用week
七、 autorelease
1.autorelease的基本用法
1> 会将对象放到一个自动释放池中
2> 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
3> 会返回对象本身
4> 调用完autorelease方法后,对象的计数器不变,池子消毁时才会改变计数器
2.autorelease的好处
1> 不用再关心对象释放的时间
2> 不用再关心什么时候调用release
3.autorelease的使用注意
1> 占用内存较大的对象不要随便使用autorelease
2> 占用内存较小的对象使用autorelease,没有太大影响
4.错误写法
1> alloc之后调用了autorelease,又调用release
@autoreleasepool
{
// 1
Person *p = [[[Person alloc] init] autorelease];
// 0
[p release];
}
2> 连续调用多次autorelease
@autoreleasepool
{
Person *p = [[[[Person alloc] init] autorelease] autorelease];
}
5.自动释放池
1> 在iOS程序运行过程中,会创建无数个池子。这些池子都是以栈结构存在(先进后出)
2> 当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池
6.自动释放池的创建方式
1> iOS 5.0前
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pool release]; // [pool drain];
2> iOS 5.0 开始
@autoreleasepool
{
}
3. 应用实例
Ø 跟release的对比
l 以前:
Book *book = [[Bookalloc]init];
[book release];
l 现在:
Book *book = [[[Bookalloc]init]autorelease];
// 不要再调用[book release];
2.开发中经常会提供一些类方法,快速创建一个已经autorelease过的对象
1> 创建对象时不要直接用类名,一般用self
+ (id)book
{
return [[[self alloc] init] autorelease];
}
外界调用[Book book]时,根本不用考虑在什么时候释放返回的Book对象
4. 规律
Ø 一般来说,除了alloc、new或copy之外的方法创建的对象都被声明了autorelease
Ø 比如下面的对象都已经是autorelease的,不需要再release
NSNumber *n = [NSNumbernumberWithInt:100];
NSString *s = [NSStringstringWithFormat:@"jack"];
NSString *s2 = @"rose";