黑马程序员——OC语言基础篇---内存管理

来源:互联网 发布:剑三淘宝金币怎么买 编辑:程序博客网 时间:2024/04/29 17:18

------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




0 0
原创粉丝点击