zoukankan      html  css  js  c++  java
  • YYKit @autoreleasepool 使用,优化内存

    写在前面

      最近再看YY大神的YYKit工具,发现在代码中经常使用@autoreleasepool,特别是在与for循环搭配使用的时候。刚开始很不能理解。

      先有个概念:

        自己创建的对象:使用 alloc new copy mutablecopy 以及他们的驼峰变形 allocObject newObject copyObject mutablecopyObject。这八种创建的才是自己创建的对象。

         不是自己创建的:除去以上八中都不是自己创建的。

         autoreleasepool:只有非自己创建的对象才会注册到离该对象最近的autoreleasepool中去。

      

      之前对iOS的ARC的理解就是自己创建的对象,会在该对象所在是代码快(当前作用域)结束时候自动释放。无需程序员自己管理。但是如果仔细研究YYKit的代码的话,发现如果注释掉@autoreleasepool,程序一样可以正常运行,但是观察当前内存,会比在加上该句代码时候占用比更大的空间。所以自己百度了一下,这里是sunny(百度一90后iOS程序员)大神写的关于@autoreleasepool的理解,主要内容其实可以概括为 一句话"Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop",具体我就不抄了,但是有些东西不适合初学者,我自己总结了一些,比较容易理解一点。具体看代码。

    @interface ViewController ()
    @property(strong, nonatomic) NSMutableArray *memoryUsageList;
    @end
    
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        //Init
        _memoryUsageList1 = [NSMutableArray new];
        
            //创建一个串行队列
        dispatch_queue_t serialQueue = dispatch_queue_create("test.autoreleasepool", DISPATCH_QUEUE_SERIAL);
        
        __block NSString *strTest;
        dispatch_sync(serialQueue, ^{
                for (int i = 0; i < kIterationCount; i++) {
                    @autoreleasepool { 
                        NSNumber *num = [NSNumber numberWithInt:i];  // 1
                        NSString *str = [NSString stringWithFormat:@"%d ", i];  // 2
                        //Use num and str...whatever...
                        strTest = [NSString stringWithFormat:@"%@%@", num, str];  // 3
                        if (i % kStep == 0) {
                            [_memoryUsageList1 addObject:@(getMemoryUsage())];  // getM方法是获取内存的函数
                            NSLog(@"000----%f", getMemoryUsage());
                        }
                    }
                }
        });
    }  // 4
    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;
    }

       让我们来分析一下当有@autoreleasepool存在的时候,代码中的1、2、3行中的内存分别是在什么时候释放。此分析建立在已经有iOS引用计数器概念的基础上。ARC其实就是编译器自动帮我们加上了retain,release等,本质还是引用计数器。

        1、

        1和2是相同的,在堆区分别存在NSNumber的对象和NSString的对象,栈区有对应的指针,num、str指向两个对象。当一次for循环结束的时候,栈区的两个指针由系统释放,此时堆区中的两个对象的引用计数器为0,所以一次for循环结束的时候,两个对象释放。

        2、

        关键是3行,首先分析strTest,每次for循环都会重新覆盖strTest之前的值,由于strTest是在viewDidLoad函数中,所以当函数结束,也就是如代码中,4行时候strTest释放,同时strTest指向的堆中的内存空间也释放。也就是for循环最后一次的"[NSString stringWithFormat:@"%@%@", num, str]"的值会释放。

        这么看来好像所有的内存都已经释放了,那么@auto究竟做了什么呢?

        重点:注意观察for循环,每一次for循环,在堆区都会生成一份"[NSString stringWithFormat:@"%@%@", num, str]"的内存,然后最新生成的一份内存会由strTest重新指一次,那么之前那些内存去哪了?我刚开始以为每一次for循环结束时候,之前生成的便会自动释放。该内存不是通过系统自动创建的,也就是说不是通过 alloc /new/copy/mutablecopy 创建的,所以会注册到autoreleasepool中 ,作用域无效,系统是无法自动释放的。这时候是@auto的关键了。@autoreleasepool相当于给堆中的内存添加了一个作用域,超过这个作用域堆中的内存就自动释放。所以每次遍历完,一份新生成的"[NSString stringWithFormat:@"%@%@", num, str]"作用域结束,自动释放。  

        那么,问题又来了。我不加@autoreleasepool,程序一样运行的很好,没出现问题。那是因为,系统在每一次的runloop中自动给我们加上了@autoreleasepool,一次runloop结束,系统就会把该循环中的所有需要release的对象清除。之所以在代码中手动添加一次,是为了防止在某一操作的时候,该操作瞬时占用的内存过大(比如for循环),导致程序瞬时的闪退。

        

        总结:多看大神代码,那写你认为很无聊的写法,可能里面包含这对整个系统更深层次的理解。

        但是发现有一些有趣的现象,例如如下的代码

        for (int i = 0; i < 100000; i++) {
            NSNumber *num = [NSNumber numberWithInt:i]; // 1
            NSString *str = [NSString stringWithFormat:@"%d ", i];  //2
            NSString *str1 = [NSString stringWithFormat:@"%@", num]; // 3
             NSString *str2 = [NSString stringWithFormat:@"%@%@", num, str]; //4
    //        NSString *str3 = [[NSString alloc] initWithFormat:@"%@%@", num, str]; // ****test****
            [NSArray arrayWithObject:@"1234"]; // 5
    //            if (i % 10000 == 0) {
    //                NSLog(@"000----%f", getMemoryUsage());
    //            }
        }

    自己个人猜测:

    单独执行代码1、2,打印的内存不会上升。  

    执行代码1和3,内存不会上升

    执行代码 1+2+4  内存飙升。猜测,不同类型合并成string内存管理不一样。

    单独执行5,内存飙升。     

    个人理解:[NSNumber numberWithInt:i]和[NSString stringWithFormat:@"%@", num]等,并没有发送autorelease消息,也就是没将他们放到autoreleasepool中。所以在代码快结束的时候他们就已经销毁了,但是某些特殊的,如[NSArray arrayWithObject:@"1234"]; 会添加到pool中,由于处于autoreleasepool中的对象延迟执行(所在runloop完毕),所以代码块结束时候,runloop并未结束,导致无法释放。

    总结:当遇到大量的for循环时候,尽量使用@autoreleasepool来避免内存的延迟释放导致瞬时内存飙升。苹果推荐以下三中情况使用@autoreleasepool代码快

       如果你编写的程序不是基于 UI 框架的,比如说命令行工具;

             如果你编写的循环中创建了大量的临时对象;(常用)

             如果你创建了一个辅助线程。

        

  • 相关阅读:
    深度学习模型训练之偏差与方差
    介绍一个快速确定神经网络模型中各层矩阵维度的方法
    TensorFlow简要教程及线性回归算法示例
    Web项目开发中常见安全问题及防范
    时序数据库及应用场景简介
    互联网产品怎么做数据埋点
    简述分布式跟踪系统实现原理
    MFC- socket 编程
    win32 socket 编程(六)——UDP
    win32 socket编程(五)——客户端实例(TCP)
  • 原文地址:https://www.cnblogs.com/XXxiaotaiyang/p/5118737.html
Copyright © 2011-2022 走看看