一:内存管理基本概念
本小节知识点:
1:当一个对象被创建完成后,内部会自动创建一个引用计数器,这个计数器是系统用来判断是否回收对象的唯一标准,当retainCount = 0的时候,系统就会马上回收当前对象
2:修改retainCount的两个方法 release和retain
release方法会使 retainCount -1;
retain 方法会使 retainCount +1;并且返回一个self指针地址,谁调用他就返回谁
3:当引用计数器 retainCount = 0的时候对象就被销毁了
4:重点:当一个对象 retainCount =0将要被销毁的时候,系统会自动调用dealloc函数,通知对象你将要被销毁了,所有为了方便观察,我们再应用的时候要重写一下这个方法
5:内存管理重重点!!!内存管理原则(配对原则),只要出现了new,alloc,copy,retain,就一定配对出现一个release或者autorelease
再学习之前我们先把ARC转变成非ARC
下面代码是创建一个Person对象,测试一下他的retainCount
int main(int argc, const char * argv[]) {
@autoreleasepool {
//一:手动内存管理基本感念
//创建对象
//1:分配内存空间,存储对象
//2:初始化成员变量
//3:返回对象的指针地址(主要初始化的就是:对象的地址,supper,weight = 0,retainCount)
//对象被创建retainCount = 1
Person *person = [[Person alloc]init];
NSLog(@"此时的retainCount 为:%zi",person.retainCount);
[person retain];
NSLog(@"此时的retainCount 为:%zi",person.retainCount);
[person release];
NSLog(@"此时的retainCount 为:%zi",person.retainCount);
[person release];
}
return 0;
}
下面是运行结果:
下面是重写的dealloc方法:
#import "Person.h"
@implementation Person
-(void)dealloc
{
//在对象被销毁之前,一定要先调用[super dealloc]释放父类中的相关对象
[super dealloc];
NSLog(@"对象被销毁了");
}
@end
二:单个对象内存管理分析
本小节重点:
内存管理研究的两个问题:
1:野指针操作
2:内存泄露(内存浪费)
我们严格按照内存管理原则(配对原则)防止这两个问题的出现
①:什么时候出现野指针情况
参考:就是在对象已经不存在了,你仍然再使用这个对象的时候,或者是你把指针地址给删除了
Person *ps = [[Person alloc]init];
ps.age = 10;
NSLog(@"person 的年龄是 %@",ps);//我们已经重写了这个description方法,返回的是age
[ps release];
//如果对象已经被掉用了,我们继续要打印这个对象,或者是这个对象的变量,那么肯定会报访问了不可访问的内存 EXC_BAD_ACCESS
//NSLog(@"person 的年龄是 %@",ps);
②:如何避免野指针异常
Person *person = [[Personalloc]init];
person.age =24;
NSLog(@"age为:%@",person);//这里当然可以写成person.age,相当于调用了getAge方法,注意前面要改成%i,不然也使野指针操作
[person release];
//当你确定当前作用域中的对象已经不会再被使用了,为了防止野指针操作,通常我们会把不再使用的指针变量赋值为nil
person = nil;
person.age =100;//相当于[nil setAge:100],调用set方法,相当于给nil发这个消息,给nil发任何消息都不会报错
[person run];
③:内存泄露的几种情况
//1:对象结束操作的时候retainCount没有等于0;这个不举例了
//2:指针没了
Person *p = [[Person alloc]init];
[p run];
//相当于你把指针里面存的地址给扔了,也就是哪个指针的箭头你给去了,所以就不能调用对象的任何东西了,但是对象仍然存在没有销毁,也就是retainCount != 0 ,所以也造成内存泄露。
p = nil;
[p release];
//[p retain],这种情况仍然是错的,因为对象已经被释放过了,你再调用对象的retain方法,仍然是野指针操作
//3:就是野指针操作,不举例了
附件:.m文件
#import "Person.h"
@implementation Person
-(void)dealloc
{
[super dealloc];
NSLog(@"Person 对象被销毁了!");
}
-(NSString *)description
{
return [NSString stringWithFormat:@"age = %i",_age];
}
-(void)run
{
NSLog(@"人跑起来了");
}
@end
三:多个对象内存管理分析和set方法内存管理分析
本小节重点:
1:在创建多个对象的时候也要按照配对原则进行创建
2:set方法内存管理平衡原则,release旧值,retain新值
格式:以 @property NSString * _name为例:
-(void) setName:(NSString *)name
{
if(_name != name)
{
[_name release];
_name = [name retain];
}
}
-(void)dealloc
{
[_name release];
}
详细讲解见下面代码:
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建一个Person对象
Person *p = [[Person alloc]init];
//创建一个Car对象
Car *car = [[Car alloc]init];
[p setCar:car];
[p drive];
[car release];
//1:我们想保证对象P存在的时候,调用P对象的任何方法都不会报错,那么我们就需要提前给car 的retainCount+1 那么加在那呢?就加在set方法当中。
//2:当我们再这里创建一个新的Car 的时候,为了防止内存泄露就需要在set方法里面多加个release,把原来的car给释放了
//3: [p setCar:car];
//注意!!!本节重点!!!!!!!
/*
上面这步:如果我们重复的调用set方法,并且给的是同一个对象,那么当遇到set方法里面的第一个release的时候,这个对象的retainCount = 0了,对象就被释放了,运行到下面的retain的时候就会报野指针错误,所以我们就需要先判断一下你传过来的对象是现在的对象还是以前的对象,就是本例中的
if(_car != car)
{
[_car release];
_car = [car retain];
}
这就是传说中的 set 方法正确的内存管理平衡原则,也就是release 旧值,retain新值,详细见set方法
*/
[p drive];
[p release];
}
return 0;
}
Person的.m文件参考:
#import "Person.h"
@implementation Person
-(void)setCar:(Car *)car
{
//首先判断一下你传过来的car是新对象,还是老对象(car1 VS car2);
if(_car != car)
{
//这里释放的原因是可能你这里存的是以前的对象,你又传过来一个新的对象,你就需要把以前的对象给释放了,当然如果是一个新对象的话给nil发送一条消息是不会报错的,所以才能达到内存平衡
[_car release];
_car = [car retain];
//这里为什么能调用retain方法呢,因为retain返回的是一个self指针,谁调用它它就返回谁按照配对原则,应该有个release,但是不能在这里,因为在这里写跟不写一个样,你刚retain,然后就又释放了等于白写,那么写在那里呢,我们把它写在delloc方法中;
}
}
-(Car *)car
{
return _car;
}
-(void)drive
{
[_car run];
}
-(void)dealloc
{
//如果一个变量被声明成了成员变量,或者是使用@property(下面会说到)生成的符合内存管理的set方法,那么一定要再 dealloc函数对应一次selease操作;
[_car release];
[super dealloc];
NSLog(@"Person被释放了");
}
@end
Car对象的.m文件参考:
#import "Car.h"
@implementation Car
-(void)dealloc
{
[super dealloc];
NSLog(@"Car被释放了");
}
-(void)run
{
NSLog(@"车跑起来了!!");
}
@end
四:@property参数管理
property参数分为四类:
<第一类>与set方法内存管理相关参数
1:retain 生成符合内存管理的set方法(应用与对象类型)
2:assign 直接赋值(没有release前值retain后值,就是直接赋值)(应用与基本数据类型和对象类型)
3:copy 生成一个新对象(下面会说)
<第二类>多线程相关
1:nonatomic不生成多线程代码,在ios开发中使用这个就可以了(使用这个效率会高点)
2:atomic 生成多线程代码(不写,默认会是这个)
//在实际开发当中,只要是对象类型就写上nonatomic;样式见.h文件
<第三类>是否set和get方法全部生成
1:readwrite:可读可写属性,同时生成set和get方法
2:readonly :只读属性,只生成get方法(比如说身份证之类的要定义成这种属性,先在构造方法的时候给它赋个值就可以了)
<第四类> set与get方法名称相关的参数
getter: 设置生成的set方法名称
setter: 设置生成的get方法名称
作用:改变set与get方法名称,多用在BOOL类型的变量
//@property 的作用
//1:生成set和get方法的声明
//2:生成set和get方法的简单shixian
//3:如果你没有声明成相对应的成员变量,那么他会自动生成一个一_开头的私有的成员变量
@property (nonatomic,retain)Car *car;
@property (nonatomic,retain)NSString *name;
@property (nonatomic,assign)int age;
@property (nonatomic,assign)int weight;
@property (nonatomic,assign,readonly)int idCard;
@property(nonatomic,assign,getter=isHeight,setter=isHeight:)BOOL height;