------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
1. 内存管理的范围
1) 管理任何继承NSObject的对象,对其他基本数据类型无效
2) 对象类型是程序运行过程中动态分配的,存储在堆区,内存管理主要是对堆区中对象的内存管理。
2. 内存管理的原理(理论)
1) 对象的所有权及引用计数
① 对象所有权概念:任何对象都可能拥有一个或多个所有者。只要一个对象至少还拥有一个所有者,它就会继续存在。
② Cocoa所有权策略:任何自己创建的对象都归自己所有,可以使用名字以“alloc”或“new”开头或名字中包含“copy”的方法创建对象,可以使用retain来获得一个对象的所有权。
③ 对象的引用计数器:每个OC对象都由自己的引用计数器,是一个整数表示对象被引用的次数,即现在有多少东西在使用这个对象。对象刚被创建是,默认计数器值为1,当计数器的值变为0是,则对象销毁。
2) 在每个OC对象内部,都专门有8个字节的存储空间来存储引用计数器。
3) 引用计数器的作用
引用计数器是判断对象要不要回收的依据(存在一种例外:对象值为nil时,引用计数为0,但不回收空间)就是计数器是否为0,若不为0则存在。
4) 对引用计数器的操作
给对象发送消息,进行相应的计数器操作:
① retain消息:使计数器+1,该方法返回对象本身
② release消息:使计数器-1(并不代表释放对象)
③ retainCount消息:获得对象当前的引用计数器值 %ld %tu
5) 对象的销毁
① 当一个对象的引用计数器为0时,那么它将被销毁,其占用的内存被系统回收。
② 当对象被销毁时,系统会自动向对象发送一条dealloc消息,一般会重写dealloc方法,在这里释放相关的资源,dealloc就像是对象的“临终遗言”。
③ 一旦重写了dealloc方法就必须调用[super dealloc],并且放在代码块的最后调用(不能直接调用dealloc方法)。
④ 一旦对象被回收了,那么它所占据的存储空间就不再可用,坚持使用会导致程序崩溃(野指针错误)。
注意:
⑤ 如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收(除非整个程序已经退出)。
⑥ 任何一个对象,刚创建的时候,引用计数器都为1.(对象一旦创建好,默认引用计数器就是1)当使用alloc、new或者copy创建一个对象时,对象的引用计数器默认就是1.
3. OC内存管理分类
1) MRC,手动管理
2) ARC,自动引用计数(iOS4.1之后)
3) 垃圾回收。
IOS中只有前两种,不支持垃圾回收。
4. 手动内存管理
1) 编译器默认使用ARC,所以首先要关闭ARC
2) 手动内存管理要重写dealloc方法
① 一定要在dealloc方法的最后调用父类的dealloc方法,[super dealloc],先释放子类占用的空间,再释放父类占用的空间
② 对self(当前)所拥有的其他对象做一次release操作
③ 永远不要直接用对象来调用dealloc方法,是系统自动调用的。
④ 一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误),为了防止调用出错,通常将“野指针”指向nil(0)。
5. 内存管理的原则
1) 原则:
① 只要还有人在使用某个对象,那么这个对象就不会被回收
② 只要你想使用这个对象,那么就应该让这个对象的引用计数器+1
③ 当你不想使用这个对象时,应该让对象的引用计数器-1.
2) 谁创建,谁release
① 如果你通过alloc,new,copy来创建了一个对象,那么你就必须调用release或者autorelease方法
② 不是你创建的就不用你去负责
3) 谁retain,谁release
只要你调用了retain,无论这个对象是如何生成的,你都要调用release
4) 总结
有始有终,有加就应该有减。曾经让某个对象计数器加1,就应该让其在最后-1.
6. 内存管理研究内容
1) 野指针
① 义的指针变量没有初始化
② 指向的空间已经被释放了
2) 内存泄露
如果栈区的指针变量已经释放了,而堆区的空间还没有释放,堆区的空间就泄露了。
7. 单个对象内存管理(野指针)
1) 对象创建完成以后,默认的所有者有一个,是自己,所以引用计数为1
2) 野指针错误:访问了一块坏的内存(已经被回收的,不可用的内存)
3) 僵尸对象:所占内存已经被回收的对象,僵尸对象不能再被使用(默认情况下xcode不会检查僵尸对象,需要自己打开检测
4) nil :是一个对象值
5) Nil:是一个对象指针
6) NULL:是一个通用指针
7) [NSNull null]是一个对象,它用在不能使用nil的场合。
8) 不能使用retain让僵尸对象起死回生。
#import <Foundation/Foundation.h>
@interface Dog : NSObject
-(void)eat;
@end
#import "Dog.h"
@implementation Dog
-(void)eat{
NSLog(@"狗在吃东西!");
}
-(void)dealloc{
NSLog(@"狗死了~~");
[super dealloc];
}
@end
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//使用MRC首先要关闭ARC
//对象创建完成后,默认引用计数为1,所有者为自己
Dog *d = [Dog new];
[d eat];
NSLog(@"d.retainCount = %lu",[d retainCount]);//1
//一个对象被释放了,就变成僵尸对象
[d release];
//对象已经被释放,其引用计数已经没有意义
//NSLog(@"d.retainCount = %lu",[d retainCount]);
//僵尸对象不能再使用
//这句话默认不会报错,是因为Xcode默认是不检查僵尸对象的,需要手动开启才能检测。
//d也就是野指针
//[d eat];
//不能用retain让僵尸对象复生
//[d retain];
}
return 0;
}
8. 单个对象的内存管理(2)
1) 避免使用僵尸对象:给僵尸对象赋值为nil
2) 内存泄露问题
① 创建完成后没有release
② 没有遵守内存管理原则
③ 不当的使用了nil
④ 在方法中错误的进行了retain
#import <Foundation/Foundation.h>
@interface Dog : NSObject
-(void)test:(Dog *)dog;
-(void)eat;
@end
#import "Dog.h"
@implementation Dog
-(void)test:(Dog *)dog{
[dog retain];
}
-(void)eat{
NSLog(@"狗在吃东西!");
}
-(void)dealloc{
NSLog(@"狗死了~~");
[super dealloc];
}
@end
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//内存泄露1
//对象创建完成,使用后没有release
Dog *d = [[Dogalloc]init];
NSLog(@"d.retainCount= %lu",[d retainCount]);//1
//没有release
//内存泄露2
//没有遵守内存管理的原则
//谁创建,谁release。谁retain,谁release
Dog *d2 = [[Dogalloc]init];
[d2 retain];
[d2 release];
NSLog(@"d2.retainCount = %lu",[d2 retainCount]);//1
//缺少一次release
//内存泄露3
//不当的使用了nil
Dog *d3 = [[Dogalloc]init];
d3 = nil;
[d3 eat]; //等同于[nil eat],已经失效
[d3 release]; //等同于[nil release],已经失效
//内存未被释放,造成泄露
//内存泄露4
//调用的方法中使用了retain
Dog *d4 = [[Dogalloc]init];
[d4 test:d4];//调用的test方法中对传入的参数d4进行了retain
[d4 release];
NSLog(@"d4.retainCount = %lu",[d4 retainCount]);//1
}
return 0;
}
9. 多个对象的内存管理
1) 野指针问题:
当一个对象调用的方法中包含另一个对象时,如果方法结束后被调用的对象被释放,则主调对象不可再调用这个方法,否则产生僵尸对象问题。
2) 内存泄露问题
只有当主调对象先释放后,再释放被调对象才能保证不发生内存泄露
10. Set方法的内存管理问题
1) 对基本数据类型,直接赋值
int float double long struct enum
-(void)setAge:(int)age{
_age= age;
}
2) OC对象类型原则:如果在一个类中,有其他类的对象(关联关系),set方法在书写的时候,要先判断是否是同一个对象,然后release旧值,retain新值
重要重要重要
-(void)setCar:(Car *)car{
//1.先判断是不是新传进来的对象
if(car!=_car){
//2.对旧对象做一次release
[_car release];//若没有旧对象,则没有影响。
//3.对新对象做一次retain
_car= [car retain];
}
}
11. @property参数
1) retain:对对象release旧值,retain新值(适用于OC对象类型)
在一个类中有关联其他对象的时候,使用retain参数,注意要重写dealloc方法,把对应对象释放一次。
@property(nonatomic,retain)Car * car;
//其set方法定义为:
-(void)setCar:(Car *)car{
If(_car!= car){
[_car release];
_car= [car retain];
}
}
2) assign:直接赋值(默认为assign,适用于非OC对象类型,即基本数据类型)
@property(nonatomic,assign)int age;等同于@property(nonatomic)int age;
3) copy:release旧值,copy新值
4) 设置set和get方法的名称
自定义set方法的名称(注意方法名的冒号):
@property(nonatomic,setter=isTrue:)int age;
对应的set方法为-(void)isTrue:(int)age{_age = age;}
同样可以自定义get方法的名称
@ property(nonatomic,setter=isTrue);
对应的set方法为-(int)isTrue{return _age;}
12. @class的使用
1) 简单的引入一个类,格式为:@class 类名
@class XXX:告诉编译器XXX是一个类,至于类有哪些属性和方法,此处不会检测
好处:如果XXX文件的内容发生了改变,不需要重新编译
2) 使用注意:
在.h文件中使用@class引入某个类,在.m文件中使用#import引入该类。
使用@class引入某个类,只是告诉编译器引入的是一个类,而无法得知这个类有哪些属性和方法,所以在.m文件中实现时,如果调用了该类的属性或方法会报错,所以使用#import 引入该类,让程序在运行时去检测。
3) @class可以解决循环引入问题
循环引入就是A引入了B,B同时引入了A,使用#import的话就会报错,可以使用@class来代替。
4) #import的作用
把要引用的头文件(Person.h)的内容,拷贝到#import处,如果要引入的文件(Person.h)内容发生了变化,此时所有引入了该头文件(Person.h)的类,都需要重新编译一次
5) @class和#import的区别
① #import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在A.h文件中B *b只是类的声明,具体这个类有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类的信息。
② 使用@class方式由于只需要知道被引用类德尔名称就可以了,而在实现类由于要用到被引用类中的实体变量和方法,所以在实现文件中需要使用#import来包含被引用类的头文件。
③ 使用#import,一旦头文件稍有改动,后面引用到这个文件的所有类都需要重新便以一遍,这样的效率太低了;而使用@class则不需要重新编译,效率要高很多。
综上:在实际开发中尽量在.h文件中使用@class来引入要包含的类
13. 循环retain问题
会当值两个对象都造成内存泄露,防止方法如下↓↓↓
1) 让某个对象多释放一次(注意顺序)
2) 推荐方法:一端使用assign,一端使用retain
14. NSString类的内存管理
1) 使用alloc initWithString:@”aaa” stringWithString:@”aaaa” @”aa”这三种方法来初始化字符串,都是位于常量区的
2)使用stringWithFormat:@”aa” alloc initWithFormat:@”aa”来初始化的字符串是位于堆区的
3)字符串的常量池:如果需要使用的字符串已经存在于常量池中,则不会重新去分配空间。
4)常量区的对象引用计数为无符号的最大值。
5)不要使用retainCount方法来作为循环条件,因为这个方法得到的值有可能是不准确的。
15. autorelease的基本使用
1) 自动释放池:
① 在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构存在的
② 当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中
③ Autorelease是一种支持引用计数的内存管理方式。它可以暂时的保存某个对象,然后再内存池自己排干的时候对其中每个对象发送release消息。
注意:这里只是发送release消息,如果当时的引用计数依然不为0,则该对象依然不会被释放。可以用该方法来保存某个对象,也要注意保存之后要释放该对象。
2) 为什么会有autorelease?
1) OC的内存管理机制中比较重要的一条规律是:谁申请,谁释放。
2) 使用autorelease,既能确保对象能正确释放,又能返回有效的对象
3) 使用autorelease:不需要再关心对象释放的时间;不需要在关心什么时候调用release。
3) Autorelease基本用法
1) 会将对象放到一个自动释放池中,创建自动释放池@autoreleasepool{ }
2) 当自动释放池被销毁时,会对池子里的所有对象做一次release。
3) 会返回对象本身
4) 对象加入自动释放池,[对象 autorelease]。调用完autorelease方法后,对象的计数器不受影响(销毁时影响-1)
4) autorelease原理:
autorelease实际上只是把对release的调用延迟了,对于每一个autorelease,系统只是把该对象放入了当前的autoreleasepool中,当该pool被释放时,该pool中的所有对象会被调用release。
5) autorelease何时释放
对于autorelease pool本身,会在以下两个条件发生时被释放:
① 手动释放autorelease pool
② Runloop结束后自动释放
③ 对于autorelease pool内部的对象,在引用计数的retain == 0 的时候释放。Release和autorelease pool的drain都会触发retain事件。
16. Autorelease使用注意
1) 并不是所有的放到自动释放池的代码,产生的对象就会自动释放,如果需要释放,必须使用autorelease方法加入到自动释放池。
2) 如果对象调用了autorelease,但是调用的时候没有在任何一个自动释放池中,此时该对象也不会被加入到自动释放池中
3) 不管创建对象的代码是否在自动释放池中,只要调用autorelease方法的代码在自动释放池中,该对象就被加入到了自动释放池中。
4) 自动释放池的嵌套
自动释放池的栈结构(数据结构),和内存的栈区是不一样的,对象时存在位于栈顶位置的自动释放池中。
5) 不建议把占内存比较大的对象加入到自动释放池中
错误用法:
1) 连续多次调用autorelease,释放池销毁时执行两次release。
2) Alloc之后调用了autorelease,之后又调用了release。
17. Autorelease的应用场景
1) 经常用来在类方法中快速创建一个对象,并且管理对象的内存。创建一个类方法,在方法中进行创建对象及添加到自动释放池中
+(instancetype)person{
Return[[[self alloc]init]autorelease];
}
2) 动态类型,程序运行的时候才知道属于什么类型
3) Instancetype可以智能的判断返回的类型和接收的类型是否一致,而id类型不会进行判断
使用autorelease快速创建方法应用:
#import <Foundation/Foundation.h>
@interface Student : NSObject
@property (nonatomic,assign)int age; //使用@property生成一个实例变量及其set和get方法
-(instancetype)initWithAge:(int)age; //重写构造方法
+(instancetype)studentWithAge:(int)age;//使用autorelease快速创建一个类
@end
#import "Student.h"
@implementation Student
//重写dealloc方法,便于查看对象是否释放
-(void)dealloc{
NSLog(@"studentdealloc");
[super dealloc];
}
-(instancetype)initWithAge:(int)age{
if (self = [super init]) {
_age = age;
}
return self;
}
+(instancetype)studentWithAge:(int)age{
return [[[selfalloc]initWithAge:age] autorelease];
}
@end
#import <Foundation/Foundation.h>
#import "Student.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu =[Student studentWithAge:19];
NSLog(@"stu.age =%d",stu.age);
}
return 0;
}
结果截图