oc篇-深入理解@autoreleasepool

一、@autoreleasepool到底是干什么的?

使用clang -rewrite-objc main.m

@autoreleasepool {
}

翻译成C++文件可知

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        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;
};

看代码可知其实是个结构体,上面的代码可以写成

atautoreleasepoolobj = objc_autoreleasePoolPush()
...
objc_autoreleasePoolPop(atautoreleasepoolobj)

二、那objc_autoreleasePoolPushobjc_autoreleasePoolPop又是什么呢?

查看runtime的objc4-646目录下的NSObject.mm源代码可以知道具体的实现

AutoreleasePoolPage {
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
}

  • magic用来校验AutoreleasePoolPage结构是否完整;
  • next指向第一个可用的地址;
  • thread指向当前的线程;
  • parent指向父类
  • child指向子类
id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }    
id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }

这两个方法快速获得可用的地址范围

每一个autoreleasepool都是由一个或多个AutoreleasePoolPage的双向链表组成的

    static size_t const SIZE       PAGE_MAX_SIZE;  // size and alignment, power of 2
    static size_t const COUNT = SIZE / sizeof(id);

展开就是

#define I386_PGBYTES        4096        /* bytes per 80386 page */
#define PAGE_MAX_SIZE           PAGE_SIZE

所以:AutoreleasePoolPage的大小都是一样的4096

关于AutoreleasePoolPage的数据结构借用一下Draveness的图

oc篇-深入理解@autoreleasepool_第1张图片
1975281-366d56087b4600cf.png

大致搞清楚了autoreleasepool的结构我们再下面从autoreleasepool的创建和对象如何加到autoreleasepool来具体说说中间都发生了什么

三、autoreleasepool的创建

当我们使用@autoreleasepool { }的时候,由上面知道实际是先调用了objc_autoreleasePoolPush方法,我们先看看objc_autoreleasePoolPush的实现

void *objc_autoreleasePoolPush(void)
{
    if (UseGC) return nil;
    return AutoreleasePoolPage::push();
}

展开其实就是

static inline void *push() 
    {
        id *dest = autoreleaseFast(POOL_SENTINEL);
        assert(*dest == POOL_SENTINEL);
        return dest;
    }

相信大家都看到了autoreleaseFast()这个方法

 static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }

这个方法就是AutoreleasePoolPage具体初始化的地方,看实现知道有三种情况

  • hotPage存在并且容量没有满,直接添加对象
  • hotPage存在但是容量已经满了,调用autoreleaseFullPage方法,初始化一个AutoreleasePoolPage并把page传入,并标记为hotPage;
  • hotPage不存在,调用autoreleaseNoPage创建一个AutoreleasePoolPage,并标记为hotePage,并且添加一个POOL_SENTINEL(哨兵对象)

上面的autoreleaseFast方法就是创建autoreleasepool的核心实现
另外看到这个关键字了吧inline,其实就是内联函数,可以提供执行速度,可以看看我之前的文章

总结:每次push会产生一个新的autoreleasepool,并生成一个POOL_SENTINEL

四、对象是如何加到autoreleasepool中去的

说完autoreleasepool的创建,接下来说对象是如何加到autoreleasepool中去的,当对象调用【object autorelease】的方法的时候就会加到autoreleasepool中

+ (id)autorelease {
    return (id)self;
}

// Replaced by ObjectAlloc
- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

展开为

inline id 
objc_object::rootAutorelease()
{
    assert(!UseGC);

    if (isTaggedPointer()) return (id)this;
    if (fastAutoreleaseForReturn((id)this)) return (id)this;

    return rootAutorelease2();
}

再展开

__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

其实就是AutoreleasePoolPage的autorelease方法

   static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        assert(!dest  ||  *dest == obj);
        return obj;
    }

是不是觉得似曾相识?是的,和之前的push方法几乎一样,就是入参不一样。

  • push方法传的是POOL_SENTINEL
  • autorelease方法是传的具体需要autoRelease对象

细心的同学肯定发现了assert(!obj->isTaggedPointer());这个断言里面的isTaggedPointer了吧,这个是苹果后面为了提高内存利用率推出来的伪指针,要想深入了解可以点击此处。

对比完autoreleasepool的创建和对象如何加到autoreleasepool我们可以得出下面的结论:

  1. 只有push操作会产生POOL_SENTINEL
  2. POOL_SENTINEL的个数就是autoreleasepool的个数,实际开发中会有嵌套使用的情况。

五、说完添加接着来说说移除pop

当执行到这个objc_autoreleasePoolPop方法的时候
autoreleasepool会向POOL_SENTINEL地址后面的对象都发release消息,直到第一个POOL_SENTINEL对象截止。

oc篇-深入理解@autoreleasepool_第2张图片
1433231260106282.jpg

我们来理解一下这个图:

  1. 首先看到有两个POOL_SENTINEL说明autoreleasepool还嵌套了一个autoreleasepool,当最新的autoreleasepool执行objc_autoreleasePoolPop方法的时候,最右边的AutoreleasePoolPage向里面的对象发release消息直到中间那个POOL_SENTINEL为止
  2. coldPage方法的定义可知最左边的那个是coldPage
 static inline AutoreleasePoolPage *coldPage() 
    {
        AutoreleasePoolPage *result = hotPage();
        if (result) {
            while (result->parent) {
                result = result->parent;
                result->fastcheck();
            }
        }
        return result;
    }
oc篇-深入理解@autoreleasepool_第3张图片
1433231268131918.jpg

依次类推。。。

六、实践

理论说完了,究竟在什么地方用这个呢,看苹果官方文档说

  1. 如果你编写的程序不是基于 UI 框架的,比如说命令行工具;
  2. 如果你编写的循环中创建了大量的临时对象;
  3. 如果你创建了一个辅助线程。

官方提供了一个例子

NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
 
    @autoreleasepool {
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL:url
                                         encoding:NSUTF8StringEncoding error:&error];
        /* Process the string, creating and autoreleasing more objects. */
    }
}

这里特地写了一个demo测试这个

static const int kStep = 50000;
static const int kIterationCount = 10 * kStep;
//查看app运行内存
- (void)obserMemoryUsage{
    NSNumber *num = nil;
    NSString *str = nil;
    for (int i = 0; i < kIterationCount; i++) {
        @autoreleasepool {
        
           num = [NSNumber numberWithInt:i];
          str = [NSString stringWithFormat:@"打哈萨克的哈克实打实的哈克时间的话大声疾呼多阿萨德爱仕达按时 "];
            
            //Use num and str...whatever...
            [NSString stringWithFormat:@"%@%@", num, str];
            
            if (i % kStep == 0) {
                double ff  =     getMemoryUsage();
                NSLog(@"%f",ff);
            }
        }
    }
}
double getMemoryUsage(void) {
    struct task_basic_info info;
    mach_msg_type_number_t size = sizeof(info);
    kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
    
    double memoryUsageInMB = kerr == KERN_SUCCESS ? (info.resident_size / 1024.0 / 1024.0) : 0.0;
    
    return memoryUsageInMB;
}

统一操作了3次我们对比一下内存占用情况

加了 @autoreleasepool {}

控制台

oc篇-深入理解@autoreleasepool_第4张图片
A0CC8527-0555-4744-A52A-B5D8005BCC01.png

不加 @autoreleasepool {}

oc篇-深入理解@autoreleasepool_第5张图片
788D40F9-EDDD-4728-AA58-69777BBC137C.png

同样是3次加了 @autoreleasepool {}内存稳定在73左右,而不加 @autoreleasepool {}的会出现暴增的情况。

@autoreleasepool {}平时不怎么关心,仔细研究起来还是挺有用处的。

reference:

  1. http://www.jianshu.com/p/32265cbb2a26
  2. http://blog.csdn.net/hepburn_/article/details/47018509
  3. https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html

你可能感兴趣的:(oc篇-深入理解@autoreleasepool)