黑马IOS学习总结1--内存管理
1、基本理论
alloc new (mutable)copy 这三者会使计数器+1
retain 返回其调用的对象本身
示例:
Person * p = [[Person alloc] init]; // retainCount 为1 ,p是在栈内存中,其内保存着指向堆内存中开创出来的Person对象地址的值。
[p retain]; // 返回p本身,即可以写成 p = [p retain] 此时retainCount 为2
[p release]; // 计数器减1 retainCount 为1
[p release]; // 计数器再减1 retainCount 为0 ,将自动调用person对象的 dealloc方法,释放内存。注意,此时,p 中仍然保存着person对象的地址值,但此时,联系已经断开了,堆内存中的person对象已经消除了,称为僵尸对象!p此时也称为野指针!
p.age = 5; // 注意,此处等于调用僵尸对象赋值,可能会不报错。在Xcode中开启内存管理开关(僵尸对象检查机制),就会报错。打开方法:Edit schema --> Diagnostics -->Objective-c Enable Zombie Objects 上打钩
[p release]; // 此时会报错。野指针指向的僵尸对象没法release;
p = nil; // 将 p 这个野指针清空,此时称为 空指针
[p release]; // 此处无错,空指针指向空对象可以release
2、SET方法内存管理
情况一:Person类中有一个属性car
-(void) setCar:(Car *) car{
_car = car; // line 1
}
这样做是不妥的,主方法中调用一下说明这个不妥:
Person * p = [[Person alloc]init];
Car * c = [[Car alloc]init];
p.car = c; // 调用上面set方法中的line 1 ,因无alloc copy new ,故 虽然 _car 使用并指向了 Car对象,但对Car对象的retainCount并无影响!
[c release]; // Car对象retainCount 为 0 ,Car 对象消失
------此处,Person类对象还没消失,但其内的_car属性已无意义,因其所指的对象已经消失--------
[p release]
情况二:为改进情况一,修改set方法如下
-(void) setCar:(Car *) car{
_car = [car retain]; // line 2
}
-(void)dealloc{
[_car release]; // 覆写dealloc方法,既然_car retain了一下Car对象,那么Person对象覆灭时候,作为其内属性的_car也应该release掉这次引用,这也是符合黄金法则的。
[super dealloc];
}
其意思是,既然_car要使用Car对象,就应该对Car对象做一次引用保留,增加一次retainCount,说明_car在这个属性在使用着Car对象。
但这样仍然是不完善的,主文件中调用一下说明这个不完善:
Person * p = [[Person alloc]init];
Car * c1 = [[Car alloc]init];
p.car = c1; // c1的Car对象 引用计数为 2
Car * c2 = [[Car alloc]init];
p.car = c2; // c2的Car对象 引用计数为 2。同时,_car 不再指向 c1的Car对象!
[c2 release]; // c2 retainCount 1
[c1 release]; // c1 retainCount 1
[p release]; // p覆灭,同时c2 retainCount 0; c1 retainCount 1,造成内存泄露
情况三:继续改进情况二
-(void) setCar:(Car *) car{
if(_car != car){
[_car release]; // 如果_car有旧值,那么这一步会使其释放所保留其指向对象的引用;如果没有旧值,等效于 [nil release] 也无不可
_car = [car retain];
}
}
3、@property的参数释义
1)有关set方法内存管理的
retain 即上面情况三,release旧值,retain新值。适用于OC对象
assign 即上面情况一,直接赋值,适用于非OC对象的基础类型(int,long,float等) 注意,默认是assign的。如果用到此项,即便默认,也要显式的写上!这是好习惯!
copy release旧值,copy新值。
2)是否要生成set方法
readwrite 同时生成setter和getter的声明和实现
readonly 只生成getter的声明和实现
3)多线程管理
nonatomic 非多线程 性能高
atomic 多线程 性能低 注意,默认是atomic的
示例:
@property(nonatomic,retain) NSString * name;
@property(nonatomic,assign) int age;
4、循环引用问题
即:A类中有一个属性是B对象,B类中有一个属性是A对象。这种情况在使用过程中,是会报错的。解决方法如下:
在一个类中用assign声明属性,另一个类中用retain声明属性即可
额外说明:在.h文件中,用 @class 方式声明一个欲使用的类,在.m文件中再用#import引用类 (如有用到该类方法需导入此类时)
5、autorelease
A 使用了autorelease,就不必再顾虑对象在何处release了,对象会被扔到自动释放池里,工作完了会自动release池子里的对象。autorelease返回对象本身。
注意:
1)使用了autorelease,对象的引用计数并不会变化。
2)占用内存较大的对象,不要随便使用autorelease,因为他会一直占用着内存,等待自动释放池销毁。反之,占用内存较小的对象,使用autorelease则没有多大影响。
3)autorelease错误写法1:
@autorelease{
Person * p = [[[Person alloc] init] autorelease]; // 计数器为 1
[p release]; // 计数器为0 此时p为野指针
} // 自动释放池在此行销毁,会再次release一次其内的对象,也即再一次[p release],而此时p是野指针了,所以会报错
错误写法2:
@autorelease{
Person * p =[[[[Person alloc] init] autorelease]autorelease];
}// 连续多次调用autorelease等效于自动释放池一销毁,会多次调用release,会引发野指针调用错误。
B autorelease实用技巧
若每次创建对象,都如此这般 Person * p = [[[Person alloc] init] autorelease]; 会显得很繁琐,尤其是多次创建对象。此时,可以在类中建立一个静态方法,示例如下:
+(id) person{ return [[[self alloc] init ] autorelease]; }
这样,每次创建对象的时候,只要 Person * p = [Person person];即可。
注意,这里用self而不是Person是有讲究的,原因是考虑到Person的子类情况。例如,GoodPerson 继承 Person类,如果此处不用self,而用Person,那么 GoodPerson *gp = [GoodPerson person];实际返回的还是Person对象,这样子类中的一些方法就无法使用了。
6、ARC
ARC的判断准则:只要没有强指针指向对象,就会释放对象。
指针分两种:
1)强指针,默认情况下,所有指针都是强指针 __strong
2) 弱指针,__weak 使用示例:__weak Person * p = [[Person alloc] init];
手动管理内存到ARC的等效转变
@propery(nonatomic,retain) -----> @property(nonatomic,strong) 适用于OC对象
@propery(nonatomic,assign) -----> @property(nonatomic,weak) 适用于OC对象
@propery(nonatomic,assign) -----> @property(nonatomic,assign) 适用于基本类型
循环引用问题在ARC中的解决:一个属性用strong,另一个用weak即可。