0
点赞
收藏
分享

微信扫一扫

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

Python百事通 2021-09-19 阅读 104
OC

官方文档

Use Local Autorelease Pool Blocks to Reduce Peak Memory Footprint

示例代码

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

  • 文件的读写处理
  • 数据的批量处理, 包括图像裁剪生成, 音视频编解码
  • 线程中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;

__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中的定义


NSObject.mm中查看实现


AutoreleasePoolPageData

NSObject-internal.h中的定义


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

一个空的 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);
    }

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

举报

相关推荐

0 条评论