美文网首页
iOS项目优化-autoreleasepool减少内存峰值

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

作者: li_礼光 | 来源:发表于2021-01-13 15:47 被阅读0次

    官方文档

    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的回调处理
    线程中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;
    
    • magic 用来校验 AutoreleasePoolPage 的结构是否完整;
    • next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin() ;
    • thread 指向当前线程;
    • parent 指向父结点,第一个结点的 parent 值为 nil ;
    • child 指向子结点,最后一个结点的 child 值为 nil ;
    • depth 代表深度,从 0 开始,往后递增 1;
    • hiwat 代表 high water mark 。

    另外,通过析构函数研究可以知道
    当 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;
    };
    
    • 先通过调用objc_autoreleasePoolPush函数来创建一个AutoreleasePool对象。
    • 然后给在代码块里创建的每个自动释放的对象发送一个autorelease消息,将这些自动释放的对象加入到AutoreleasePool对象里。
    • 最后在AutoreleasePool对象将要销毁时,通过调用objc_autoreleasePoolPop函数给池中每个自动释放的对象发送一次release消息,再销毁AutoreleasePool对象。

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

    简单粗暴总结 :

    • 注意理解AutoreleasePool本身是什么, AutoreleasePool和runloop的关系
    • 使用AutoreleasePool, 在autoreleasePool里面的对象会在合适的时间段内释放, 什么是合适的时间段, 每一次runloop的时候,Entry(即将进入Loop)到Before waiting(准备进入休眠)会处理一次AutoreleasePool
    • AutoreleasePool也是一个对象, 其他添加在AutoreleasePool中的对象本质做的是延迟释放, AutoreleasePool释放时统一给所有对象发送一次release消息。

    回到最开始的代码

    • 情况一:循环过程中,创建的NSString对象一直在堆积,只有在循环结束才一起释放,所以内存一直在增加。
    • 情况二:每一次迭代中都会创建并销毁一个AutoreleasePool,而每一次创建的NSString对象都会加入到AutoreleasePool中,所以在每次创建AutoreleasePool都会调用AutoreleasePoolPage::tls_dealloc时,NSString对象就会被释放,这样内存就不会增加。

    每一次循环 , 每一次创建的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的基本概念

    相关文章

      网友评论

          本文标题:iOS项目优化-autoreleasepool减少内存峰值

          本文链接:https://www.haomeiwen.com/subject/pdnuaktx.html