知识点内存OC 底层

深入理解@autoreleasepool

2017-05-02  本文已影响619人  fanglaoda

@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;
}

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

1975281-366d56087b4600cf.png

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

1.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具体初始化的地方,看实现知道有三种情况

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

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

2.说完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方法几乎一样,就是入参不一样。

细心的同学肯定发现了assert(!obj->isTaggedPointer());这个断言里面的isTaggedPointer了吧,这个是苹果后面为了提高内存利用率推出来的伪指针,这个后面我会另有介绍,这里可以先忽略。

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

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

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

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

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;
    }
1433231268131918.jpg

依次类推。。。

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

官方提供了一个例子

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 {}
控制台

A0CC8527-0555-4744-A52A-B5D8005BCC01.png

不加 @autoreleasepool {}

788D40F9-EDDD-4728-AA58-69777BBC137C.png

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

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

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

上一篇 下一篇

猜你喜欢

热点阅读