------Java培训、Android培训、iOS培训、.Net培训 期待与您交流! -------
OC的对象不会自动释放,如果不进行管理的话,程序运行没多久内存就该不够了,所以内存管理对于OC编程来说,是非常需要注意的点,当然,这是没有ARC的时候,现在有了ARC,内存管理就不用我们多管了,但是,我们还是应该了解一下内存管理的原理的。
首先,应该先了解一个小知识点
引用计数器
每个OC对象都有自己的引用计数器,是一个整数,表示“对象被引用的次数”,即有多少人正在使用这个OC对象,占据4个字节的存储空间
作用是:当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1;当一个对象的引用计数器值为0时,对象占用的内存就会被系统回收。换句话说,如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收,除非整个程序已经退出
使用retain和release来操作计数器,利用retainCount方法来获取当前引用计数器的值。
对象的销毁
当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收
当一个对象被销毁时,系统会自动向对象发送一条dealloc消息
一般会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言
一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用
不要直接调用dealloc方法
一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误:指针指向僵尸对象)
概念
1> 僵尸对象 所占用的内存已经被回收的对象,僵尸对象不能再使用
2> 野指针 指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错(EXC_BAD_ACCESS)
3> 空指针 没有指向任何东西的指针(存储的东西是NULL,nil,0),给空指针发送消息不会报错
个人感觉,retain和release有点像操作系统中的wait和signal。
内存管理的原则
(1)谁创建,谁release
也就是,只要调用了alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease
(2)谁retain,谁release
对象调用了retain,就必须release
下面以set方法的内存管理作为例子来感受一下吧(好不容易摆脱了setter和getter,又回去了)
设计两个类Student、Car和Dog,组合关系
Car类
#import <Foundation/Foundation.h>
@interface Car : NSObject
{
int _speed;
}
- (void)setSpeed:(int)speed;
- (int)speed;
@end
#import "Car.h"
@implementation Car
- (void)setSpeed:(int)speed
{
_speed = speed;
}
- (int)speed
{
return _speed;
}
- (void)dealloc
{
NSLog(@"速度为%d的Car对象被回收",_speed);
[super dealloc];
}
@end
Dog类里什么都没有
Student类
@interface Student : NSObject
{
int _no;
NSString *_name;
Car *_car;
Dog *_dog;
}
- (void)setNo:(int)no;
- (int)no;
- (void)setName:(NSString *)name;
- (NSString *)name;
- (void)setCar:(Car *)car;
- (Car *)car;
- (void)setDog:(Dog *)dog;
- (Dog *)dog;
@end
#import "Student.h"
@implementation Student
- (void)setNo:(int)no
{
_no = no;
}
- (int)no
{
return _no;
}
- (void)setName:(NSString *)name
{
if (name != _name)
{
[_name release];
_name = [name retain];
}
}
- (NSString *)name
{
return _name;
}
- (void)setCar:(Car *)car
{
if (car != _car) {
[_car release];
_car = [car retain];
}
}
- (Car *)car
{
return _car;
}
- (void)setDog:(Dog *)dog
{
if (dog != _dog) {
[_dog release];
_dog = [dog retain];
}
}
- (Dog *)dog
{
return _dog;
}
- (void)dealloc
{
[_name release];
[_car release];
[_dog release];
NSLog(@"Student对象被回收了");
[super dealloc];
}
@end
main.m
Student *s1 = [[Student alloc] init];
Car *c = [[Car alloc] init];
c.speed = 200;
s1.car = c;
s1.name = @"Jack";
[c release];
[s1 release];
运行结果
2015-04-15 19:39:20.047 03-set方法的内存管理[4699:303] 速度为200的Car对象被回收
2015-04-15 19:39:20.049 03-set方法的内存管理[4699:303] Student对象被回收了
这里严格遵守了内存管理的原则,main函数里有alloc,那么最后一定要release
set方法中
if (car != _car) {
[_car release];
_car = [car retain];
}
这一段表示的意思是,如果值需要修改,那么将原来对象release,在对新对象执行retain,并将新对象的值赋给成员变量。
dealloc中进行方法重写
[_name release];
[_car release];
[_dog release];
NSLog(@"Student对象被回收了");
[super dealloc];
需要将所有的对象成员变量释放,并在最后调用父类的dealloc方法。
set方法的代码规范
基本数据类型不用管理内存,直接赋值
OC对象
1.先判断是不是新传进来的对象
2.对旧对象做一次release操作
3.对新对象做一次retain操作,并赋值给成员变量
dealloc方法的代码规范
1.[super dealloc],放在最后
2.对当前拥有的对象做一次release
好了,接下来要讲的是,上面那堆都是废话,下面让我们看看如何在ARC下使用@property来减轻我们的工作量吧!
在讲这个之前,先引入两个知识点
1.autorelease
autorelease意味着自动释放空间?No!No!No!
它并不是自动释放,而是将对象放在自动释放池,当自动释放池被销毁的时候,对对象做一次release
基本用法
@autoreleasepool 自动释放池开始
{
Person *p = [[[Person alloc] init] autorelease]; 采用autorelease方法把对象放到自动释放池中,返回对象本身
p.age = 10;
} 自动释放池销毁,里面的所有对象做一次release
通过这个,不用再担心,release代码写到对象调用之前,造成野指针错误
autorelease可以嵌套调用无数次,调用完autorelease后,对象的计数器不变
好处:不用再关心对象释放的时间,不用关心什么时候调用release
使用注意: 占用内存较大的对象不要随便使用autorelease,应该使用release来精确控制对象释放的时间
占用内存较小的对象都可以使用,没有太大影响
常见错误
1> alloc之后调用autorelease,再调用release,这样会报野指针错误
2> 连续多次调用autorelease,当自动释放池被销毁的时候,会进行多次release,也是会报野指针错误
调用autorelease时,会把对象放进栈顶的池子里
自动释放池
1>在IOS程序运行过程中,会创建无数个池子,这些池子都是以栈结果存在(先进后出)
2>当一个对象调用autorelease方法时,会将这个对象放进栈顶的释放池
autorelease在实际开发中的应用
1) 一般会写一个类方法,返回一个autorelease对象
+ (id)person
{
return [[[self alloc] init] autorelease];
}
2) 系统自带的方法里面没有包含alloc,new,copy,说明返回的对象都是autorelease的
开发中经常会提供一些类方法,快速创建一个已经autorelease过的对象
3 )当在类方法中创建对象的时候,不要直接用类名,尽量使用self,可以实现用一个方法,创建不同类型的对象
2.property参数
(1).内存管理相关的参数
(非ARC)retain:release旧值,retain新值 适用于OC对象
(ARC)strong:强指针(一般都用这个)
weak:弱指针,用弱指针指向对象不影响其被释放,弱指针的使用主要是在接下来将要讲到的循环引用里。
assign 直接赋值,默认就是这个,适用于非OC对象类型
copy release旧值,copy新值
(2).是否生成set方法
readonly 只会生成getter
readwrite 可读可写,默认就是这个
(3).多线程管理
nonatomic 不加锁,性能高
atomic 加锁(应用于多线程),性能低,这是默认的
(4).setter和getter方法的名称
@property (setter = setAbc:,getter=abc) int age;
这样可以变更方法名,但是不会改变成员变量的名
点语法仍然可以适使用
这样一般是用在BOOL类型的getter方法,规范是Bool类型getter时,命名以is开头
3.ARC
这是编译器特性 :在编译的时候自动生成release的代码(不是java中的垃圾回收)
ARC的判断准则
只要没有强指针指向对象,就会释放对象
只要弱指针指向对象被销毁,那么弱指针也被清空
4.循环引用
当两个类互相引用的时候
在.h文件中,使用@class 类名 声明是个类,不用import 类的头文件,在.m文件实现时,再import 头文件
@class
1. 作用,仅仅告诉编译器,这是一个类,(缺点:仅知道是一个类,而不知道类的所有方法)
2. 开发中的一个类的规范
1)在.h文件中用@class来声明类
2)在.m文件中用#import来包含类的所有东西
@class的好处
a.这样是为了性能,在.h文件中如果#import,这样会降低编译器的效率
b.解决循环引用的问题
两端循环引用解决方法(非ARC)
一端用retain
另一端用assign (dealloc方法中,不用release)
不这样,两个对象谁也没有办法被释放
下面就在ARC下,使用property的参数来解决循环引用的问题吧
定义两个类Person和Dog,让他们循环引用
Person类
#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject
@property (nonatomic,strong) Dog *dog;
@end
注意:
@class就是上面讲的声明这是一个类,而没有使用import
@property (nonatomic,strong) Dog *dog;
这一句在上面讲到循环引用问题时曾提过,在非ARC环境下(一端用retain,另一端用assign,)
@property (nonatomic,retain) Dog *dog;
#import "Person.h"
@implementation Person
- (void)dealloc
{
NSLog(@"Person被销毁");
}
@end
这里dealloc方法还是需要重写的,便于监听对象什么时候被销毁,但是,注意了,对比上面非ARC的时候,对象的release不需要了。
Dog类
#import <Foundation/Foundation.h>
@class Person;
@interface Dog : NSObject
@property (nonatomic,weak) Person *person;
@end
上面提到过,非ARC情况下
@property (nonatomic,assign) Person *person;
#import "Dog.h"
@implementation Dog
- (void)dealloc
{
NSLog(@"Dog被销毁");
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Dog.h"
#import "Person.h"
int main() {
@autoreleasepool
{
Person *p =[[Person alloc] init];
Dog *d = [[Dog alloc] init];
p.dog = d;
d.person = p;
}
return 0;
}
运行结果
2015-04-15 20:23:29.056 12-ARC的循环引用[4977:303] Person被销毁
2015-04-15 20:23:29.057 12-ARC的循环引用[4977:303] Dog被销毁
比起上面set方法的内存管理,这样非常简洁,而且不需要考虑什么时候release。
ARC下内存管理总结
1.不允许调用retain,release,retainCount
2.允许重写dealloc,但是不允许调用[super dealloc]
3.@property参数
strong:成员变量是强指针,适用于OC对象
weak:成员变量是弱指针,适用于OC对象
assign:适用于非OC对象
4.以前的retain--->strong