Objective - C基础: 第五天 - 1.计数器的基本认识

前言

在我们OC中, 有一个东西是重中之重的知识点, 那就是内存管理, 什么是内存管理呢? 其实内存管理是我们app在运行的时候所使用的内存大小, 在iOS中, 给我们应用设定了一个固定的内存值, 一旦超过这个值, 系统就会给app发送内存警告, 如果这个警告不处理, 那么就会强制关闭应用, 就是我们所说的闪退, 那么内存有多少种呢? 内存其实是分为五种, 分别为堆, 栈, 全局区, 文字常量区, 程序代码区, 在实际开发中, 我们关注的最多的是堆和栈, 其余三个有兴趣的朋友自行去了解.



开始

在我们没有学习OC内存管理机制的时候, 我们来看一个例子, 看看我们之前所写的代码是怎么样管理内存的:

#import <Foundation/Foundation.h>

@interface Person : NSObject
@end

@implementation Person
@end

int main()
{
    int a = 1;

    int b = 2;

    Person *p = [[Person alloc]init];
	
    return 0;
}


示意图




在例子里面, 我们知道a, b, *p都是局部变量, 一旦所在的代码块执行完之后就会消失不见, 但Person对象并不会不见, 而是会一直存在于内存中, 我们都知道OC一开始的时候就会加载所有的类, 给它们分配存储空间, 而这个存储空间就是堆, 那么我们怎么去释放这一块内存空间呢? 其实说到底就是调用一个方法, 给Person这个类发送一条消息, 让它自己释放, 那就可以解决我们的需求.




在OC中, 这种内存管理方式称为计数器, 而存在于堆里面的叫做引用计数, 如果要让堆里面的空间被释放, 唯一的办法就是让引用计数变为0, 这样子系统就会自动回收引用计数为0的类, 其实这样子也就是, 只要引用计数不为0的类, 就会一直存在于内存中, 不会被释放.




那么在什么情况下才会产生引用计数呢? 其实在我们一刚开始创建对象的时候引用计数就会产生, 也就是alloc,new, 还有一个我们暂时没有学到的copy, 所产生的引用计数默认为1, 而这个引用计数的类型是int类型, 大小为4.



谁再去调用, 那么引用计数就会+1, 谁释放就会-1, 以此类推.


PS: 这里的+1就是给对象发送retain消息, 而发送release那么引用计数就会-1, 发送retainCount消息, 就会返回当前引用计数的数值.




那还有没有更加简单直接的方式知道对象被释放了呢, 其实在对象被释放的时候, 系统会自动调用一个叫做dealloc的方法, 而我们可以重写dealloc方法, 就可以知道对象是否有没有被释放.



下面让我们一起来探讨一下吧:

#import <Foundation/Foundation.h>

@interface Person : NSObject
@end

@implementation Person
- (void)dealloc
{
    NSLog(@"对象被回收了");
    [super dealloc];
}
@end

int main()
{
    Person *p = [[Person alloc]init];

    NSUIntger a = [p retainCount];

    NSLog(@"a = %d", a);

    [p release];

    return 0;
}


打印出来的结果是:

2015-01-25 11:55:45.619 1.引用计数器的基本操作[2092:163711] a = 1
2015-01-25 11:55:45.620 1.引用计数器的基本操作[2092:163711] 对象被回收了

PS:[super dealloc];这句代码一定要卸载dealloc方法的最后面, 否则就会出错, 原理就是先释放自己, 然后再释放父类.




这里说一下, 其实release并不是释放对象的意思, 而是引用计数减一, 如果你使用了alloc 又使用了retain, 那么引用计数就是2, 写一个release是不能释放的, 必须得有两个, 直到引用计数为0之后才会释放对象, 有增就必须得有减.




那有人会突发奇想, 既然是这样子, 那我就多写几个release吧, 这样也是不对的, 下面让我们来看看:

#import <Foundation/Foundation.h>

@interface Person : NSObject
@end

@implementation Person
- (void)dealloc
{
	NSLog(@"对象被回收了");
	[super dealloc];
}
@end

int main()
{
	Person *p = [[Person alloc]init];

	NSUIntger a = [p retainCount];

	NSLog(@"a = %d", a);

	[p release];

	[p release];
	
	return 0;
}

如果是这样子写, 那么程序在运行中就会报错, 报下面这个错误:(一般我们称为野指针错误)

EXC_BAD_ACCESS(code=1, address=0x18)




为什么会报这个错? 其实原理很简单, 在OC中, 一个引用计数变为0的对象, 我们称为僵尸对象, 就是说内存不可用的对象, 而指向僵尸对象的指针我们称为野指针, 当对象被释放掉的时候, 只要指针的地址不清零, 那么指针和对象的关系就不会消失, 所以一旦当我们给一个内存不可用的对象继续release, 那么软件就会崩溃, 而在后面的所有代码都不能运行.




所以在编程的时候, 我们要注意引用计数的增减是否对应, 一旦引用计数不为0, 那么对象就会不被释放, 如果引用计数为0之后还继续release, 那么就会报错, 这个我们需要注意一下.



如果想解决这个问题, 有两种方法, 一种是删掉多余的release, 另一种是把指针变成空指针, 在OC中, 给空指针做操作是不会报错的, 只是会有一个警告而已, 比如:

int main(int argc, const char * argv[])
{
    Person *p = [[Person alloc]init];
    
    NSUInteger a = [p retainCount];
    
    NSLog(@"a = %ld", a);
    
    [p release];
    
    p = nil;
    
    [p release];
    
    return 0;
}

结果:

2015-01-25 13:40:26.188 1.引用计数器的基本操作[2251:185241] a = 1
2015-01-25 13:40:26.189 1.引用计数器的基本操作[2251:185241] 对象被回收了

还是和我们之前得到的结果一样~~~



总结一下:

1.方法的基本使用

1> retain :计数器+1,会返回对象本身

2> release :计数器-1,没有返回值

3> retainCount :获取当前的计数器

4> dealloc

* 当一个对象要被回收的时候,就会调用

* 一定要调用[super dealloc],这句调用要放在最后面

 

2.概念

1> 僵尸对象 :所占用内存已经被回收的对象,僵尸对象不能再使用

2> 野指针 :指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错(EXC_BAD_ACCESS

3> 空指针 :没有指向任何东西的指针(存储的东西是nilNULL0),给空指针发送消息不会报错

 






好了这次我们就讲到这里, 下次我们继续~~~

posted @ 2015-01-25 13:49  背着吉他去流浪  阅读(292)  评论(0)    收藏  举报