Objective-C内存管理之引用计数

Apple在iOS5之后引入了自动ARC技术,让编译器来自动来管理内存,开发者从此不用再去写很多retain、release代码了。ARC下,编译器完全清楚目标对象,并且能够自动释放那些需要释放的对象,因此,程序内存泄漏的风险大大降低了,但!ARC!不!是!万!能!的!
在实际开发中,内存泄漏还是比较常见的,比如使用block时候的不注意,导致的循环引用;比如在使用NSTimer实例的时候,timer的action事件执行者也就是Target会强引用Self,如果timer没有正确的销毁,就会造成内存泄漏等等;这个时候我们就需要来手动管理内存了。因此,很有必要知道Objective -C的内存管理机制。

二、Objective-C内存管理是机制,就是引用计数。

正确客观的思考方式:
1)自己生成的对象,自己持有。
2)非自己生成的对象,自己也能持有。
3)自己持有的对象,一旦不再需要,就需要立即释放。
4)非自己持有的对象,自己无法释放。

这里的“自己”,知道是当前的对象的使用环境,也可以理解为程序猿自己啦。

问题一:
什么情况下自己生成的对象,自己持有?

使用以下名称开头的方法名,意味着自己生成的对象只有自己持有:
a)alloc

// 自己生成并持有
    id obj = [[NSObject alloc] init];

使用类的alloc方法就能生成一个对象,在这里,指向生成并持有的对象的指针被赋值给变量obj。

b ) new

//    自己生成并持有对象
    id obj2 = [NSObject new];

在Objective-C中,[[NSObject alloc] init]和[NSObject new]是完全一致的。
c)copy
d )mutableCopy
copy和mutableCopy都是生成并持有对象的一份副本,虽然持有的是副本,但是,副本是自己生成的。

另外,除以上的几个,以下名称开头的方法也意味着自己生成并持有。
(1)allocMyObject
(2)newThatObject
(3)copyThis
(4)mutableCopyYourObject
但是对于一下名称,即使用alloc/new/copy/mutableCopy名称开头,并不属于同一类方法。
(1)allocate
(2)newer
(3)copying
(4)mutableCopy

问题二:
什么情况下自己非自己生成的对象,自己也能持有?
用alloc/new/copy/mutableCopy等开头以外的方法取得的对象,因为不是自己生成的对象,所以并不持有这个对象。比如一些类方法。

//    取得非自己生成的对象,自己并不持有

    id array = [NSMutableArray array];

//    如果要持有对象,那么就向对象发送retain消息
    [array retain];

取到一个非自己生成的对象,但是自己可以持有对象的实现如下,向对象发送autorelease消息,告诉系统这个对象需要被引用,延缓释放:

- (id) object{

    id obj = [[NSObject alloc] init];
    [obj autorelease]; //obj 放入到NSAutoreleasePool中,延迟释放。
    //取得的对象存在,但是自己不持有对象
    return obj;
}

其实,由类方法创建的对象都会放入到NSAutoreleasePool中。后面会继续讲。

问题三:
如何释放自己持有的对象?
向自己持有的对象发送release消息。

//      自己生成并持有
    NSArray *arr1 = @[@"11",@"22"];

//    释放自己生成且持有的对象
    [arr1 release];



//    取得非自己生成的对象,自己并不持有

    id array = [NSMutableArray array];

//    如果要持有对象,那么就向对象发送retain消息
    [array retain];

//      释放自己持有的对象
    [array release];

这里有个需要弄明白的:

1)释放 !== 销毁
一个对象可以有多个持有者,且持有者只能向自己持有的对象发送release消息,由引用计数器原则,当一个对象的retainCount == 0,这个对象才会被销毁。多个持有者时,每次release都会使对象的retainCount - 1。
实例如下代码:

//    创建并持有一个对象。
    id obj = [NSObject new];
    NSLog(@" create new obj  === %lu ===",[obj retainCount]);//retainCount=1

    NSMutableArray *mutableArray = [NSMutableArray array];
//    数组对象持有 对象 obj
    [mutableArray addObject:obj];
    NSLog(@" mutableArray have obj === %lu ===",[obj retainCount]); //retainCount=2

    id dict = [NSMutableDictionary dictionary];
//    字典对象持有 NSObject对象 obj
    [dict setObject:obj forKey:@"oc"];
    NSLog(@" dict have obj === %lu ===",[obj retainCount]);//retainCount=3

//    字典对象释放
    [dict release];
    NSLog(@" dic release === %lu ===",[obj retainCount]);//retainCount=2

//    数组对象释放
    [mutableArray release];
    NSLog(@" mutableArray release === %lu ===",[obj retainCount]);//retainCount=1

//    obj对象释放
    [obj release];
    NSLog(@" obj release === %lu ===",[obj retainCount]);//retainCount=1

所以,释放跟销毁不是一个概念,它们并不等价。

2)赋值 !== 持有
下面这句代码,NSMutableArray类对象被赋给变量obj,但变量obj自己并不持有改对象。所以,赋值并不等价于就是持有。

id array = [NSMutableArray array];

问题四:
对于“非自己持有的对象,自己无法释放“这句话的解释。
其实就是被释放或被销毁的对象不能重复被引用。

//    创建并持有一个对象。
    id obj = [NSObject new];
    NSLog(@" create new obj  === %lu ===",[obj retainCount]);

    //    obj对象释放
    [obj release];
    NSLog(@" obj release === %lu ===",[obj retainCount]);

    //    obj对象释放
    [obj release];
    NSLog(@" obj release === %lu ===",[obj retainCount]);

第二次释放的时候程序会崩溃。

二、alloc/retain/release/dealloc

关于alloc/retain/release/dealloc的实现可参看GNUstep,亦可以查阅 《Objective - C高级编程》这本书上的讲解。这里就不赘述了。

三、引用计数的实现

这一块儿理解的不是很好,大致的是这么回事儿:alloc类方法首先调用allocWithZone:类方法,然后调用create_Instance函数,然后通过调用calloc方法来分配内存。Apple内部使用一个引用计数表来管理所有对象的引用计数,同时,引用计数表还记录着每个对象的内存块地址。每次调用release方法都会去技术表中查询对象的引用计数的值。

具体总结如下:
(1)在Objective-C这一对象中存在引用计数器这一整数值。
(2)调用alloc 或者retain方法后,引用计数值会加1.
(3)调用release后,引用计数值减1.
(4)引用计数值为0时,调用dealloc方法废弃对象。

你可能感兴趣的:(OC内存管理)