iOS项目优化-autoreleasepool减少内存峰值

2021-01-13  本文已影响0人  li_礼光

官方文档

Use Local Autorelease Pool Blocks to Reduce Peak Memory Footprint

Many programs create temporary objects that are autoreleased. These objects add to the program’s memory footprint until the end of the block. In many situations, allowing temporary objects to accumulate until the end of the current event-loop iteration does not result in excessive overhead; in some situations, however, you may create a large number of temporary objects that add substantially to memory footprint and that you want to dispose of more quickly. In these latter cases, you can create your own autorelease pool block. At the end of the block, the temporary objects are released, which typically results in their deallocation thereby reducing the program’s memory footprint.

The following example shows how you might use a local autorelease pool block in a for loop

示例代码

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. */
    }
}




简单测试代码 :

- (void)viewDidLoad {
    [super viewDidLoad];
    for (int i = 0; i<1000; i++) {
        @autoreleasepool {
            NSError *error;
            NSURL *url = [NSURL URLWithString:@"https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html#//apple_ref/doc/uid/20000047-SW5"];
            NSString *fileContents = [NSString stringWithContentsOfURL:url
                                                              encoding:NSUTF8StringEncoding error:&error];
            if (i == 999) {
                NSLog(@"fileContents : %@", fileContents);
            }
        }
    }
}

不使用@autoreleasepool 使用@autoreleasepool

从这里可以总结考虑, 为了保持项目中的内存使用情况, 保持流畅, 在项目中的相关地方应该是用autoreleasepool

比如:

文件的读写处理
文件的读写处理
线程中block的回调处理
线程中block的回调处理









深入研究 :

通过clang指令把main.m转为main.cpp文件

cd到想要编译的文件目录
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

main.m

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

main.cpp

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

查找 __AtAutoreleasePool __autoreleasepool;

image.png

__AtAutoreleasePool

创建 : objc_autoreleasePoolPush
销毁 : objc_autoreleasePoolPop

从源码中找 :

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


NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

AutoreleasePoolPage

NSObject-internal.h中的定义


AutoreleasePoolPage

NSObject.mm中查看实现


AutoreleasePoolPage

AutoreleasePoolPageData

NSObject-internal.h中的定义


image.png

那这里的 AutoreleasePoolPage 是什么东西呢?

NSObject.mm相关源码

一个空的 AutoreleasePoolPage 的内存结构如下图所示:

代码结构
    magic_t const magic;
    __unsafe_unretained id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;

另外,通过析构函数研究可以知道
当 next == begin() 时,表示 AutoreleasePoolPage 为空;
当 next == end() 时,表示 AutoreleasePoolPage 已满。

~AutoreleasePoolPage() 
    {
        check();
        unprotect();
        ASSERT(empty());

        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        ASSERT(!child);
    }
    bool empty() {
        return next == begin();
    }

    bool full() { 
        return next == end();
    }



回到最初

代码块的实现逻辑如下:

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

注意区分AutoreleasePool对象和自动释放的对象,AutoreleasePool对象指的是实例化的一个自动释放池(本质也是对象),而 自动释放的对象是指被加入到这个池中的对象。

简单粗暴总结 :

回到最开始的代码

每一次循环 , 每一次创建的NSString对象都会加入到AutoreleasePool:

    static void init()
    {
        int r __unused = pthread_key_init_np(AutoreleasePoolPage::key, 
                                             AutoreleasePoolPage::tls_dealloc);
        ASSERT(r == 0);
    }

AutoreleasePoolPage::tls_dealloc)

   static void tls_dealloc(void *p) 
    {
        if (p == (void*)EMPTY_POOL_PLACEHOLDER) {
            // No objects or pool pages to clean up here.
            return;
        }

        // reinstate TLS value while we work
        setHotPage((AutoreleasePoolPage *)p);

        if (AutoreleasePoolPage *page = coldPage()) {
            if (!page->empty()) objc_autoreleasePoolPop(page->begin());  // pop all of the pools
            if (slowpath(DebugMissingPools || DebugPoolAllocation)) {
                // pop() killed the pages already
            } else {
                page->kill();  // free all of the pages
            }
        }
        
        // clear TLS value so TLS destruction doesn't loop
        setHotPage(nil);
    }

if (!page->empty()) objc_autoreleasePoolPop(page->begin()); // pop all of the pools
调用了objc_autoreleasePoolPop

参考:
Objective-C Autorelease Pool 的实现原理
自动释放池的前世今生 —- 深入解析 Autoreleasepool
ARC的原理详解
iOS原理 AutoreleasePool的基本概念

上一篇 下一篇

猜你喜欢

热点阅读