ios 底层

Objective-C内存篇(三) - Autorelease

2018-08-16  本文已影响128人  Tenloy

什么是自动释放池

自动释放,也是延迟释放。
自动释放池的实现原理或者说作用:在自动释放池被销毁或耗尽时,会向池中的所有对象发送release消息,释放所有autorelease对象。

自动释放池中的使用

//MRC下
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    [pool drain];   //相当于[obj release];
//ARC下
    @autoreleasepool{
        NSArray __autorelasing * arr = [[NSArray alloc] init];
        或者
        NSArray * arr = [NSArray arrayWithObject:@""]; // alloc/new/copy/mutableCopy之外的方法,生成的对象,默认是自动释放池管理(MRC与ARC下)
    }

Autorelease Pool的实现原理

# NSAutoreleasePool对应的AutoreleasePoolPage类 - 相关代码

ARC下,我们使用@autoreleasepool{}来使用一个AutoreleasePool,随后clang -rewrite-objc可编译成下面代码:

//被编译的代码
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
    @autoreleasepool{
        id obj = [[NSObject alloc] init];
    }
    return 0;
}

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);
struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

//略作改动,对应MRC下的代码
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc) init];/*等同于objc autoreleasePoolPush ( ) */
id obj = [[NSObject alloc) init];
[obi autorelease];/*等同于objc autorelease (obi ) */
[pool drain];/*等同于obic autoreleasePoolPop (pool ) */

而这几个函数都是对AutoreleasePoolPage的简单封装,所以自动释放机制的核心就在于这个类

可通过objc4库的runtime/objc-arr.mm来确认苹果中autorelease的实现。
objo4/runtime/objc-arr.mm class AutoreleasePoolPage:

class AutoreleasePoolPage
{
    static inline void *push (){  //相当于生成或持有NSAutoreleasePool类对象; 
    }
    static inline void *pop (void *token){  //相当于废弃NSAutoreleasePool类对象; 
        releaseAll(); 
    }
    static inline id autorelease (id obj){  //相当于NSAutoreleasePoo1类的addobject类方法
        AutoreleasePoolPage *autoreleasePoolPage =取得正在使用的AutoreleasePoolPage实例;
        autoreleasePoolPage->add (obj );
    }
    id *add (id obj){//将对象追加到内部数组中;
    }
    void releaseAll (){//调用内部数组中对象的release实例方法;
    }
};

void *objc autoreleasePoolPush (void){
    return AutoreleasePoolPage: :push ();
}
void objc autoreleasePoolPop (void *ctxt){
    AutoreleasePoolPage: :pop (ctxt);
}
id *objc autorelease (id obj){
    return AutoreleasePoolPage: :autorelease (obj);
}

# AutoreleasePoolPage类

AutoreleasePoolPage是一个C++实现的类


AutoreleasePoolPage

参数解读:

AutoreleasePoolPage 的存储结构

一个AutoreleasePoolPage的空间被占满时(next == end()时),会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入

# 释放机制

每当进行一次objc_autoreleasePoolPush调用时,runtime就向当前的AutoreleasePoolPage中add进一个哨兵对象,值为0(也就是个nil),那么这一个page就变成了下面的样子:

image

objc_autoreleasePoolPush的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(哨兵对象)作为入参,于是:

  1. 根据传入的哨兵对象地址找到哨兵对象所处的page
  2. 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release消息,并向回移动next指针到正确位置
  3. 补充2:从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page

# 嵌套的AutoreleasePool

pop的时候总会释放到上次push的位置(上次push时返回的哨兵对象地址)为止,多层的pool就是多个哨兵对象而已,互不影响。

__autoreleasing所有权修饰符

ARC有效时,用@autoreleasepool块替代NSAutoreleasePool类,用附有__autoreleasing修饰符的变量替代autorelease方法

加入自动释放池的几种方法

NSArray * array = [NSArray arrayWithCapacity: 1];
等同于
NSArray * array = [[[NSArray alloc] initWithCapacity:1] autorelease];
因为_weak 修饰符只持有对象的弱引用,而且在访问引用对象的过程中,该对象可能被废弃。如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保对象的存在。

# 打印自动释放池中的对象

可通过NSAutoreleasePool类中的调试用非公开类方法showPools来确认已被autorelease的对象的状况。showPools会将现在的NSAutoreleasePool的状况输出到控制台。

[NSAutoreleasePool showPools];  

或者直接使用_objc_autoreleasePoolPrint()函数来打印(无论ARC是否有效)

/* 函数声明 */
extern _objc_autoreleasePoolPrint();

/* 调用 */
_objc_autoreleasePoolPrint();

自动释放池的释放时机

1. 生成大量的临时变量
2. 生成大容量对象
    1. UIImage转NSData : UIImageJPEGRepresentation / UIImagePNGRepresentation 这两个方法在转为NSData的时候,这些Data都会写到内存中,如果图片太多,太大,就会导致内存暴涨
    2. [UIImage imageNamed: ]  会读入内存,所以相对的,速度也是最快的,Interface Builder(sb,xib)就是通过这个方法来加载的,图片被缓存,导致内存过大
以上这些,都需要我们及时清理对象、内存,避免造成内存占用过高

for (int i = 0; i < 10000; ++i) {
       @autoreleasepool{
          NSString *str = @"Hello World";
          str = [str stringByAppendingFormat:@"- %d",i];
        } //此时,自动释放池的释放时机就是在此处:大括号完成的时候
 }

注意:使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // 这里被一个局部@autoreleasepool包围着
}];

参考链接:
http://blog.sunnyxx.com/2014/10/15/behind-autorelease
http://www.cocoachina.com/ios/20150610/12093.html
参考书籍:《Objective-C 高级编程》

上一篇下一篇

猜你喜欢

热点阅读