------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
Xcode5.0之后已经让新建的项目强制带上ARC机制,但是内存管理依然是oc学习的核心之一。下面学习一下早起编写程序时是如何管理内存问题的。
先取消ARC机制,Xcode6.1中取消项目ARC机制方法:如下所示,将ARC处选择NO就好了
但也有些时候使用了ARC机制的项目要引入一些非ARC模式的第三方库文件,这时需要在文件编译选项上做些操作,加入一段代码:-fno-objc-arc 如图:
另外 -f-objc-arc 的意思就是要文件使用ARC的模式编译。
先说对象回收以及引用计数的概念。
每个对象创建后都会有一个类似计数器的东西记录着对象被指针指向的个数,和该计数器相关的方法有如下几个:
retain:计数器加一
release:计数器减一
autorelease:和自动释放池一起用也可以使计数器减一
retainCount:返回当前计数器的值
而对象销毁的原理就是当对象的引用计数器值变为0时就会被销毁。
因此,传统上编写程序时,有关对象内存管理的代码都是如下方式编写,以Person类和Dog类为例。
——-Person.h文件
#import
@class Dog;
@interface Person : NSObject
{
@private
Dog *_dog;
}
- (Dog*) dog;
- (void) setDog:(Dog*)dog;
@end
——-Person.m文件
#import "Person.h"
#import "Dog.h"
@implementation Person
- (Dog *)dog{
return _dog;
}
//set方法中要注意对oc对象成员变量做release和retain操作
- (void)setDog:(Dog *)dog{
if(_dog != dog){
[_dog release];
_dog = [dog retain];
}
}
//dealloc方法中也要对oc对象的成员变量做release操作
- (void)dealloc{
NSLog(@"Person 被回收了。");
[_dog release];
[super release]; //最后一定要调用此方法释放父类的资源
}
@end
———main.m文件中
#import
#import "Person.h"
#import "Dog.h"
int main() {
Person *p = [[Person alloc]init]; // person引用计数为 1
Dog *dog1 = [[Dog alloc]init]; // dog引用计数为 1
p.dog = dog1; // dog引用计数变为 2
[dog1 release]; //dog引用计数减1后从2变为1
dog1 = nil; //避免野指针错误
[p release]; //person引用计数减1后从1变为0将被回收,
//Person的dealloc方法将dog对象的引用计数再减1变为0,dog也被回收
p=nil; //避免野指针错误
return 0;
}
程序运行结果:
2014-12-18 22:01:05.313 ocblog9[516:20144] Person 被回收了。
2014-12-18 22:01:05.314ocblog9[516:20144] Dog 被回收了。
以上例子中可以看出内存管理需要注意的地方在如下几处:
1.对象刚创建出来起引用计数器值初始为1,需要在使用完毕后执行一次release操作。
2.set方法中需要对oc对象的成员变量做判断以及release旧的值,retain新的值。
3.dealloc方法中要先release本类中的oc对象,再在最后一行调用父类的[super dealloc]方法。
总之就是遵循谁创建谁释放,谁retain谁release的原则,一对一就行。既不可多,也不可少,否者就会造成内存泄露或者野指针错误的问题。
如上Person类中的Dog*类型的成员变量还可以改成如下形势:
——-Person.h文件
#import
@class Dog;
@interface Person : NSObject
@property(nonatomic,retain) Dog* dog;
@end
——-Person.m文件
#import "Person.h"
#import "Dog.h"
@implementation Person
//dealloc方法中也要对oc对象的成员变量做release操作
- (void)dealloc{
NSLog(@"Person 被回收了。");
[_dog release];
[super release]; //最后一定要调用此方法释放父类的资源
}
@end
如上重点就是改成了@property的方式,其中retain代码的作用就是起到了原先set方法中的作用,然而面对非oc对象的成员变量时则不用retain,使用assign表示直接赋值不做内存管理的操作。
但是dealloc方法中依然要release资源。
如上就是早期编写内存管理的代码方式,但是还有一个重要细节要弄清,就是对象间的循环依赖问题。
解决办法是在@property中一方使用retain,另一方使用assign。
Person.h文件中声明Dog*类型变量:
@property(nonatomic,retain)Dog* dog;
Dog.h文件中声明Person*类型变量:
@property(nonatomic,assign)Person *person;
如此就不会发生循环引用依赖的问题。
这种方式在写程序时较为繁琐,总是要手动在程序中release对象,不太方便,因此xcode后来推出了一个autolease的功能,代码示例如下:
——-修改main.m文件内容如下
#import
#import "Person.h"
#import "Dog.h"
int main() {
@autoreleasepool {
Person *p = [[[Person alloc]init]autorelease]; // person引用计数为 1
Dog *dog1 = [[[Dog alloc]init]autorelease]; // dog引用计数为 1
p.dog = dog1; // dog引用计数变为 2
}
return 0;
}
程序运行结果:
2014-12-18 22:29:15.563 ocblog9[516:20144] Person 被回收了。
2014-12-18 22:29:15.564ocblog9[516:20144] Dog 被回收了。
可以看到autorelease的作用也是将引用计数器的值减一,但是减去的时机推迟了。
其中@autoreleasepool{}是一个引用计数池,该引用计数池可以有多个,也可以嵌套,它的生命周期就是大括号之内的范围。当对象在引用计数池代码块中调用了autorelease后该对象就会被放入引用计数池栈中最外面的引用计数池中,当该计数池的代码块运行完后,会给放入其中的所有对象执行一次release操作。
所以@autoreleasepool的功能就是延缓对象执行release的操作时间而已,并非代码块结束立即释放对象,而autorelase方法也只是将对象放入引用计数池中。
基于以上特性,@autoreleasepool让我们不必考虑对象释放代码的位置,便于统一管理。但同时该方法只适合比较小的对象,由于延缓释放对象,会对性能造成一定影响。