内存管理的基本概念及范围
内存管理:系统会向app发送memory waring消息,收到消息后,需要回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等,否则程序会崩溃。
管理范围:管理任何继承NSObject的对象,对其他的基本数据类型无效(对象和其他数据类型在内存的存储位置不一样)
内存管理主要是对堆区中对象的内存管理
内存管理的原理及分类
原理:
对象的所有权及引用计数:
任何对象都可能拥有一个或多个所有者,只要一个对象至少还拥有一个所有者,它就会继续存在
引用计数器的作用:
在每个OC对象的内部,都有专门8个字节的存储空间来存储引用计数器,判断对象要不要回收(存在一种例外,对象值为nil,引用计数为0,但不回收空间)
对引用计数器的操作:1、retain消息:是计数器+1,该方法返回对象本身
2、release消息:是计数器-1(并不代表会释放)
3、retainCount消息:获得对象当前的引用计数器值
对象的销毁:当一个对象的引用计数器为0时,name它将被销毁,其占用的内存被系统回收。当对象被销毁时,系统会自动向对象发送dealloc消息,一般会重写dealloc方法。一旦重写dealloc方法,必须调用[super dealloc],并且放在代码块的最后调用(不能直接调用dealloc方法)。
一旦对象被回收了,那么该空间就不可再用,坚持使用会导致程序崩溃(野指针错误)。
当使用alloc、new和copy出的对象,默认引用计数器为1
内存管理分类:
OC提供了三种内存管理方式:1、Mannul Reference Counting(MRC,手动管理,在开发iOS4.1之前的版本的项目时,我们要自己负责使用引用计数来管理内存,比如要手动retain、release、autorelease等,而在其后的版本可以使用ARC,让系统自己管理内存)
2、automatic reference counting (ARC,自动引用计数)
3、garbage collection(垃圾回收,iOS不支持垃圾回收)
开发中如何使用:需要理解MRC,但实际使用时尽量用ARC
手动内存管理快速入门
//
用
person
实例一个对象
Person *p = [ Person new ];
// 证明 p 有一个所有者
NSUInteger count = [p retainCount ]; // 用一个 8 字节无符号 long 类型来存储
NSLog ( @"count = %lu" , count);
// 是引用计数器 +1
// 让 p 的所有者增加 1 ,让 p1 去指向
Person *p1 = [p retain ];
NSLog ( @"p1.retainCount = %lu" , [p1 retainCount ]);
// 回收对象,使 retainCount == 0
[p release ];
NSLog ( @"p.retainCount = %lu" , [p retainCount ]);
Person *p = [ Person new ];
// 证明 p 有一个所有者
NSUInteger count = [p retainCount ]; // 用一个 8 字节无符号 long 类型来存储
NSLog ( @"count = %lu" , count);
// 是引用计数器 +1
// 让 p 的所有者增加 1 ,让 p1 去指向
Person *p1 = [p retain ];
NSLog ( @"p1.retainCount = %lu" , [p1 retainCount ]);
// 回收对象,使 retainCount == 0
[p release ];
NSLog ( @"p.retainCount = %lu" , [p retainCount ]);
[p release];//p执行后,此时对象被回收销毁,自动调用dealloc方法
重写dealloc方法
//dealloc
方法,是对象的临终遗言的方法
// 对象被销毁的时候,会默认的调用该方法
// 注意: dealloc 方法是系统自动调用的,不要手动调用
-( void )dealloc{
// 先释放子类子集的对象空间
NSLog ( @"Person 已经挂了 " );
// 再释放父类的
[ super dealloc ];
// 对象被销毁的时候,会默认的调用该方法
// 注意: dealloc 方法是系统自动调用的,不要手动调用
-( void )dealloc{
// 先释放子类子集的对象空间
NSLog ( @"Person 已经挂了 " );
// 再释放父类的
[ super dealloc ];
}
内存管理的原则
内存管理:
对象如果不使用了,就应该回收他的空间,防止造成内存泄露
内存管理的范围:
所有的继承了
NSObject
的对象的内存管理,基本数据类型除外
内存管理的原则:
1
、原则:只要还有人使用某个对象,该对象就不能被回收
只要你想使用这个对象,就应该让这个独享的引用计数器
+1
,
retain
当你不想使用时,应该让对象的引用计数
-1
,
release
2
、谁创建,谁
release
3
、谁
retain
,谁
release
4
、总结:有始有终,有加就有减
内存管理研究的内容:
1
、野指针:
1
)定义的指针变量没有初始化
2
)指向的空间已经被释放了
2、内存泄露:栈区的P已经释放了,而指向的堆区的空间还没释放,堆区的空间就被泄露了
//
创建:
new
、
alloc
、
copy
Dog *bigYellowDog = [ Dog new ]; //1
NSLog ( @"bigYellowDog.retainCount = %lu" , [bigYellowDog retainCount ]);
Dog *jd = [bigYellowDog retain ]; // 新的对象,两个对象地址一样
NSLog ( @"jd.retainCount = %lu" , [jd retainCount ]);
//<Dog: 0x100206760>,<Dog: 0x100206760>
NSLog ( @"%@,%@" , bigYellowDog, jd);
// 谁创建,谁 release
[bigYellowDog release ];
Dog *bigYellowDog = [ Dog new ]; //1
NSLog ( @"bigYellowDog.retainCount = %lu" , [bigYellowDog retainCount ]);
Dog *jd = [bigYellowDog retain ]; // 新的对象,两个对象地址一样
NSLog ( @"jd.retainCount = %lu" , [jd retainCount ]);
//<Dog: 0x100206760>,<Dog: 0x100206760>
NSLog ( @"%@,%@" , bigYellowDog, jd);
// 谁创建,谁 release
[bigYellowDog release ];
[jd release];
最后,当retainCount == 0,系统自动调用dealloc方法
单个对象内存管理(野指针)
Xcode在默认情况下,为了提高效率,不会开启僵尸对象检测Zombie->Edit scheme->run->
Dog
*byd = [
Dog
new
];
[byd eat];
NSLog
(
@"byd.retainCount = %lu"
, byd.
retainCount
);
//
可以使用
.
调用此方法
[byd
release
];
//
逻辑上释放,物理上不可能删除,已经被释放的对象就是僵尸对象
//
这就是野指针使用,已经释放了的空间。但是让僵尸对象去
eat
,不合理
//
默认是不报错的,设置开启僵尸对象检测
[byd
eat
];
//
同理,用已经释放的僵尸对象访问
retainCount
方法读取引用计数器的值,也是野指针使用
NSLog(@"byd.retainCount = %lu", byd.retainCount);
注意:
1、
nil:是一个对象值,赋值为空,便是nil
Nil:是一个类对象值
NULL:是一个通用指针(泛型指针)
[NSNull null]:是一个对象,用在不能使用nil的场合
2、不能使用僵尸对象,使其死而复生
[d
release
];
//
僵尸对象死而复生
,
并未开启
Zombie
,不会报错
[d retain ];
//nil 给 nil 发送任何消息都没有效果
// 避免使用僵尸对象的方式:对象释放完了以后,给对象赋值为 nil, 提高程序健壮性
[d retain ];
//nil 给 nil 发送任何消息都没有效果
// 避免使用僵尸对象的方式:对象释放完了以后,给对象赋值为 nil, 提高程序健壮性
d = nil;
[d
retain
];//再使用僵尸对象,就不会报错,不会响应任何消息
3、野指针操作
单个对象的内存泄露问题
1、创建和使用完后,没有release
2、没有遵守内存管理原则,新增的retain要和release对应
3、不当的使用nil,在release之前,使用nil赋值。导致最后并没有release,因为nil不会响应任何消息
4、在方法的内部retain,但是在main中只release一次,也满足了内存管理的原则,此时的retain比较隐蔽,要注意
多个对象的内存管理(野指针)
Person
*fengjie = [
Person
new
];
Car
*bigBen = [
Car
new
];
bigBen. speed = 180 ;
// 给凤姐一辆车
[fengjie setCar :bigBen];
[fengjie goLasa ];
[bigBen release ]; //1->0 Car 销毁
// 这句话报错的原因 goLasa 方法中使用了 _car( 就是 bigBen) 而 bigBen 已被销毁
// 要想下面的语句不报错,必须保证 _car 存在
bigBen. speed = 180 ;
// 给凤姐一辆车
[fengjie setCar :bigBen];
[fengjie goLasa ];
[bigBen release ]; //1->0 Car 销毁
// 这句话报错的原因 goLasa 方法中使用了 _car( 就是 bigBen) 而 bigBen 已被销毁
// 要想下面的语句不报错,必须保证 _car 存在
//要想在[bigBen release]之后,_car还存在 必须bigBen的引用对象不止一个
//
即至少
[bigBen retain]
一次
,
但是这种方法明显比较笨
//还可以在setCar方法里这样赋值_car = [car retain];这样比较健壮
//
但是这样之后,依旧存在
[bigBen release]
的问题
//
为了保证,人没亡,就可以调用车
//我们可以在dealloc方法里进行[bigBen release]-》[_car release]
[fengjie goLasa];
set方法的内存管理
原则:在一个类中,有其他类的对象(关联关系)
基本数据类型作为实例变量:直接赋值_speed = speed;
对象类型作为另一个类的实例变量:先判断是否是同一个对象,然后先release旧值,在retain新值
-(
void
) setCar:(
Car
*)car {
//
如果
_car == car
就是同一个对象
// 同一个对象, release 之后就不可以在 retain 了,否则 zombie 就复活了
if ( _car != car) {
// 先释放前一个车,release旧值
// 同一个对象, release 之后就不可以在 retain 了,否则 zombie 就复活了
if ( _car != car) {
// 先释放前一个车,release旧值
[_car release];
//retain新值,并且赋值给实例变量
_car
= [car
retain
];
}
@property参数
4.4之前:
1)@property+手动实现
2)@property int age; + @synthesize age;//get和set方法的声明和实现都帮我们做了
3)
@property int age; + @synthesize age = _age;//指定值赋值
4.4增强
@property int age;
1)生成_age
2)生成_age的get和set方法的声明和实现
格式:@property(参数1,参数2。。)数据类型 方法名
参数类别:
1、原子性:1)atomic:对属性加锁,多线程下线程安全,默认值
2)nonatomic:对属性不加锁,多线程下不安全,但是速度快
2、读写属性:1
)readwrite:生成setter和getter方法,默认值
2)readonly:只生成getter方法
3、set方法处理:1)assign:直接赋值,默认值
2)retain:先release原来的值,再retain新值
3)copy:先release原来的值,再copy新值
替换set和get方法名称:@property(setter = isVip:,getter = isVip)
//setVip: == isVip:
//vip == isVip
//同样可以使用点语法
@class的使用
#import作用:把要引用的头文件内容,拷贝到写#import处
//如果引用的头文件内容发生变化,则引用到这个文件的所有类就需要重新编译,效率低
@class的使用:
格式:@class 类名;
@class XXX;
含义:告诉编译器,XXX是一个类,至于类有哪些属性和方法,此处不去检测
好处:如果XXX文件内容发生变化,不需要重新编译
注意:由于不知道属性和方法,所以不可以直接在其他类中去使用,如何要使用,需要在实现文件中导入该类头文件
所以,在实际开发中,.h中使用@class,.m中使用#import
1).h @class XX;
2).m #import “XX.h"
3)@class解决循环引入问题:交叉引用,循环依赖,使用#import会报错
循环retain的问题
Person
*p = [
Person
new
];
Dog *d = [Dog new];
p.dog = d; //p,d互掐
d.
owner
= p;
[d release ];
[d release ];
[p release];
[p release];//虽然也可解决问题,但有可能由于顺序不同,导致某个对象多释放一次
循环的retain会导致两个对象都会内存泄露
推荐的方法:一端使用assign直接赋值,一端使用retain
NSString类的内存管理
@“abc”是字符串常量
栈区高地址,栈区-》堆区-》BSS-》数据区-》代码区
//
定义字符串
//字符串的常量池
//
如果你需要的字符串在常量池已经存在了,不会分配内存空间
//
使用字符串的时候,
//@"abc" stringWithString alloc initWithString
都在常量区
//str2
和
str4
如果在常量区,地址应该一样
//
但是地址不一样,在堆区
NSString
*str1 =
@"abc"
;
//
常量区
NSString
*str2 = [
NSString
stringWithFormat
:
@"aaa"
];
NSString
*str3 = [
NSString
stringWithString
:
@"abc"
];
//
常量区
NSString
*str4 = [[
NSString
alloc
]
initWithFormat
:
@"aaa"
];
NSString
*str5 = [[
NSString
alloc
]
initWithString
:
@"abc"
];
//
常量区
NSLog
(
@"str1 = %@,%p,%lu"
, str1, str1, str1.
retainCount
);
NSLog
(
@"str2 = %@,%p,%lu"
, str2, str2, str2.
retainCount
);
NSLog
(
@"str3 = %@,%p,%lu"
, str3, str3, str3.
retainCount
);
NSLog
(
@"str4 = %@,%p,%lu"
, str4, str4, str4.
retainCount
);
NSLog(@"str5 = %@,%p,%lu", str5, str5, str5.retainCount);
危险用法:
retainCount对于系统有时候不准,自己的对象要把握retain和release呼应
while([a retainCount] > 0){
[a release];
}//死循环
autorelease基本使用
1、什么是autorelease?
自动释放池:1)在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的
2)当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中
iOS5.0之后,创建方式
@autoreleasepool
{//开始
。。。。
}//结束
autorelease:是一种支持引用计数的内存管理方式
它可以暂时的保存某个对象,然后在内存池自己排干的时候对其中每个对象发release消息(每个对象只发送一次),注意,这里只是发送release消息,如果当时的retaiCount依然不为0,则该对象不会被释放。可以用该方法保存某个对象,也要注意保存之后要释放该对象。
自动释放池的使用:
1)创建自动释放池
2)加入自动释放池,对象的引用计数不会改变
[对象 autorelease];
3)自动释放池销毁,会对池子里的所有对象release一次
2、为什么会有autorelease?
OC内存管理原则:谁申请,谁释放。如果一个方法需要返回一个对象,该对象合适释放?方法内部是不会写release来释放对象的,因为这样立即释放会返回一个空对象;调用者也不会主动释放该对象,因为遵循“谁申请,谁释放”的原则。那么此时,就发生了内存泄露。
针对这种情况,OC设计了autorelease,既能保证对象正确释放,又能返回有效地对象
autorelease好处:
1)不需要关心对象释放时间
2)不需要关心什么时候调用release
3、autorelease的原理?
autorelease实际上只是把对release的调用延迟了,将对象一直保留到autoreleasepool释放时,pool中的所有对象都会调用release
4、autorelease什么时候被释放?
1)5.0之前手动释放
2)5.0之后自动释放
autorelease的注意及错误使用
1)并不是所有放到自动释放池中的代码,产生的对象就会自动释放。如果需要释放,必须加入自动释放池
Person
*p = [[
Person
new
]
autorelease
];
2)如果对象调用了autorelease,但是调用的时候,没有放在任何一个自动释放池中,此对象也不会被加入自动释放池
3)尽量不要把内存较大的对象放到自动释放池中
4)不要在一个释放吃中,多次使用[p autorelease];
5)在alloc后,自动释放池结束之后,又进行
[p autorelease]操作,不允许。zombie操作
autorelease的应用场景
1、快速创建对象的类方法
Person
*p = [
Person
person
];
//person 类方法 : 快速创建对象,并且管理对象的内存(加入自动释放池)
//1 )创建一个对象 P
//person 类方法 : 快速创建对象,并且管理对象的内存(加入自动释放池)
//1 )创建一个对象 P
//2)用完之后,系统把对象释放掉
+(
id
) person
{
// 能够创建对象
// 能够帮我们把对象加入自动释放池
return [[[ Person alloc ] init ] autorelease ];
{
// 能够创建对象
// 能够帮我们把对象加入自动释放池
return [[[ Person alloc ] init ] autorelease ];
}
2、完善快速创建对象的方法
//
创建一个学生对象
Student *stu = [ Student person ]; // 返回的是 Person 类型
Student *stu = [ Student person ]; // 返回的是 Person 类型
[stu run];//动态类型,程序运行时才知道是什么类型,其实是[Person run];
只有将类方法修改一下才可以
+(
id
) person
{
// 能够创建对象
// 能够帮我们把对象加入自动释放池
// 谁调用 返回谁
//Person 调用 返回 [Person run];
//Student 调用 返回 [Student run];
//Person--- 》 self
return [[[ self alloc ] init ] autorelease ];
{
// 能够创建对象
// 能够帮我们把对象加入自动释放池
// 谁调用 返回谁
//Person 调用 返回 [Person run];
//Student 调用 返回 [Student run];
//Person--- 》 self
return [[[ self alloc ] init ] autorelease ];
}
3、最终完善类方法
NSString
*str = [
Student
person
];
NSLog(@"str.lenth = %ld", str.length);
没完善之前,返回值类型是Student,指针类型是NSString。但是编译不会报错
将id类型改为instancetype类型:能够智能的检测指针类型和返回值类型是否一致
+(
instancetype
) person
{
// 能够创建对象
// 能够帮我们把对象加入自动释放池
// 谁调用 返回谁
//Person 调用 返回 [Person run];
//Student 调用 返回 [Student run];
//Person--- 》 self
return [[[ self alloc ] init ] autorelease ];
// 能够创建对象
// 能够帮我们把对象加入自动释放池
// 谁调用 返回谁
//Person 调用 返回 [Person run];
//Student 调用 返回 [Student run];
//Person--- 》 self
return [[[ self alloc ] init ] autorelease ];
}
当我们需要指定值的创建对象和初始化,我们可以
-(
instancetype
) initWithAge:(
int
)age
{
//1 、先初始化父类的,并且判断是否成功
if ( self = [ super init ]) {
//2 、初始化子类
_age = age;
}
//3 、返回 self
return self ;
}
//+(instancetype) student
//{
// return [[self alloc] initWithAge:18];
//}
// 这样就写死了
+( instancetype ) studentWithAge:( int )age
{
return [[ self alloc ] initWithAge :age];
{
//1 、先初始化父类的,并且判断是否成功
if ( self = [ super init ]) {
//2 、初始化子类
_age = age;
}
//3 、返回 self
return self ;
}
//+(instancetype) student
//{
// return [[self alloc] initWithAge:18];
//}
// 这样就写死了
+( instancetype ) studentWithAge:( int )age
{
return [[ self alloc ] initWithAge :age];
}
此时的类方法,可以传递参数