• AutoreleasePool 分析


    前言

    AutoreleasePool自己主动释放池,对于自己主动释放对象的作用怎样?
    释放池中的自己主动释放对象什么时候会被释放?

    MRC环境下

    场景1

    NSString *string_var_ = nil;
    - (void)viewDidLoad {
        [super viewDidLoad];
        NSString *string = [NSString stringWithFormat:@"bluefish"];
        string_var_ = string;
    
        NSLog(@"viewDidLoad--string: %@", string);
    }
    
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        NSLog(@"viewWillAppear--string: %@", string_var_);
    }
    
    - (void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        NSLog(@"viewDidAppear--string: %@", string_var_);
    }
    

    控制台打印:

    viewDidLoad--string: bluefish
    viewWillAppear--string: bluefish

    然后,NSLog(@"viewDidAppear--string: %@", string_var_);
    野指针报错。
    当中,NSString *string = [NSString stringWithFormat:@"bluefish"];是自己主动释放对象,引用计数为1,string对其引用,在viewDidLoad和viewWillAppear返回时,string对象还没有被release,到viewDidAppear时被release了。也就是说string对象在viewWillAppear和viewDidAppear之间被线程的自己主动释放池release。

    那假设string是非自己主动释放的呢。

    NSString *string = [[NSString alloc] initWithFormat:@"%@",@"bluefish"];

    则三个方法都会打印,但由于string一直没有显式调用relase方法,造成string说引用的[[NSString alloc] initWithFormat:@"%@",@"bluefish"]一直没释放。不符合手动内存管理原则。

    场景 2

    假设显式使用autoreleasepool自己主动释放池,代码:

    - (void)viewDidLoad {
    @autoreleasepool {
            NSString *string = [NSString stringWithFormat:@"bluefish"];
            string_var_ = string;
        }
    NSLog(@"viewDidLoad--string: %@", string_var_);
    }

    运行后,NSLog(@"viewDidLoad--string: %@", string_var_);野指针报错。
    说明。autorelease创建时(引用计数为1)。会自己主动增加到autoreleasepool{}自己主动释放池里。结束时,会把里面的自己主动释放对象作一次release(引用计数-1),所以string_var_成了野指针。而局部变量string作用域结束被销毁(出栈)。因此log打印时。string_var_野指针报错。

    那autoreleasepool里的非autorelease对象会不会被释放呢,我们试试:

    @autoreleasepool {
            NSString *string = [[NSString alloc] initWithFormat:@"%@",@"bluefish"];
            string_var_ = string;
        }

    结果是三个方法都打印出来了,说明。非autorelease对象不会被增加autoreleasepool。

    其情况就跟 “场景1” 一样,string一直得不到释放。

    场景3

    假设显式使用autoreleasepool自己主动释放池,并且将string定义在autoreleasepool{}外会怎么样,例如以下

    - (void)viewDidLoad {
        [super viewDidLoad];
    
         NSString *string = nil;
        @autoreleasepool {
            string = [NSString stringWithFormat:@"bluefish"];
            string_var_ = string;
        }
    
        NSLog(@"viewDidLoad--string: %@", string_var_);
    }

    运行后,情况跟“场景2”一样,无法成功打印。野指针报错,由于[NSString stringWithFormat:@"bluefish"]为自己主动释放对象,string对其做引用。一旦出了autoreleasepool{},string所指向的[NSString stringWithFormat:@"bluefish"]便会被释放掉。string和string_var_都成了野指针。
    那假设换成[[NSString alloc] initWithFormat:@"%@",@"bluefish"]会怎么。答案是跟“场景2”一样,对象引用一直有效。由于一直没有release。

    小结:在普通情况下,创建的autorelease对象会被增加到离自己近期的释放池,假设没显式使用autoreleasepool{},则增加到当前线程的释放池中。一旦释放池结束,里面的对象都会做一次release。
    注意即使显式地使用autoreleasepool{},里面的非自己主动释放对象也要手动release,否则对象一直不会释放。

    以上是MRC(手动内存管理)下。自己主动释放池autoreleasepool对对象的处理差异。

    但在ARC下会怎样呢。

    ARC环境下

    场景1

    这里我们使用_weak修饰外包变量string_weak。依据arc的规则。string_weak_不正确他引用的对象做持有。不会影响引用的对象的释放,当所应用对象被释放时,string_weak_会自己主动被设为nil。所以这里用弱引用变量string_weak_来观察对象的释放,以下看代码:

    __weak NSString *string_weak_ = nil;
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        // 场景 1
        NSString *string = [NSString stringWithFormat:@"bluefish"];
        string_weak_ = string;
    
        NSLog(@"viewDidLoad--string: %@", string_weak_);
    }
    
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        NSLog(@"viewWillAppear--string: %@", string_weak_);
    }
    
    - (void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        NSLog(@"viewDidAppear--string: %@", string_weak_);
    }

    运行后控制台输入:

    viewDidLoad--string: bluefish
    viewWillAppear--string: bluefish
    viewDidAppear--string: (null)

    从打印接口可看string对象在viewWillAppear和viewDidAppear之间被释放。这个释放时机与MRC下是一样的,仅仅只是MRC下变成野指针报错而已。但理解方式就不一样了。
    [NSString stringWithFormat:@"bluefish"]创建时,被自己主动增加所在线程的自己主动释放池中。这样,它的释放就交给了释放池去管理了。然后,string对其做引用,由于arc下。默认是_strong,所以string对其做了一次强引用。而当string出了viewDidLoad方法。局部变量string就会被销毁。这时,string对[NSString stringWithFormat:@"bluefish"]的强引用就消失了,可是由于[NSString stringWithFormat:@"bluefish"]还在自己主动释放池中未被释放,所以string_weak还能够引用到它,直到当前线程自己主动释放池释放[NSString stringWithFormat:@"bluefish"]

    那么假设是非自己主动释放对象呢:

    NSString *string = [[NSString alloc] initWithFormat:@"%@",@"bluefish"];

    运行打印结果:

     viewDidLoad--string: bluefish
     viewWillAppear--string: (null)
     viewDidAppear--string: (null)

    结果显示[[NSString alloc] initWithFormat:@"%@",@"bluefish"]一离开viewDidLoad方法。就被释放了。

    由于,[[NSString alloc] initWithFormat:@"%@",@"bluefish"]为非自己主动释放对象,创建时。不会被增加自己主动释放池中,所以它的释放是在arc机制下的。当强引用对象string离开作用域时。被销毁,[[NSString alloc] initWithFormat:@"%@",@"bluefish"]失去全部的强引用,没有不论什么一个强引用对象指向它,因此arc对其进行了release。

    场景2

    显式使用autoreleasepool自己主动释放池。代码:

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        @autoreleasepool {
            NSString *string = [NSString  stringWithFormat:@"bluefish"];
            string_weak_ = string;
        }
        NSLog(@"viewDidLoad--string: %@", string_weak_);
    }
    

    运行打印结果:

     viewDidLoad--string: (null)
     viewWillAppear--string: (null)
     viewDidAppear--string: (null)

    从结果看。[NSString stringWithFormat:@"bluefish"]一出autoreleasepool{}就被释放了。

    分析一下,
    跟“场景1”一样,[NSString stringWithFormat:@"bluefish"]创建时。被自己主动增加autoreleasepool{}自己主动释放池中,这样,它的释放就交给了释放池去管理了。

    然后,string对其做强引用,并且string是在autoreleasepool{}中定义的,当autoreleasepool{}结束时,string被销毁。强引用消失。而这时,autoreleasepool{}自己主动释放池也结束了。因此[NSString stringWithFormat:@"bluefish"]也被释放掉。

    那假设换成非自己主动释放对象呢。

     @autoreleasepool {
            NSString *string = [[NSString alloc] initWithFormat:@"%@",@"bluefish"];
            string_weak_ = string;
        }

    打印结果还是一样,why?以下分析,
    由于[[NSString alloc] initWithFormat:@"%@",@"bluefish"]非自己主动释放,所以他不会增加到autoreleasepool,而是靠arc来管理内存,string对其作强引用。

    但autoreleasepool{}结束时,局部变量string销毁。而arc下,[[NSString alloc] initWithFormat:@"%@",@"bluefish"]一旦失去了不论什么强引用,也会被释放。所以。最后一出autoreleasepool{}就都被释放了。

    PS:能够使用lldb指令 watchpoint set v string_weak_ 设置观察点。观察 string_weak_ 变量的值的变化。

    先设置一个断点,然后设置watchpoint set v string_weak_ 。继续运行程序。假设string_weak_改变(变为nil),就会被检測到。

    watchpoint set v string_weak_
    Watchpoint created: Watchpoint 1: addr = 0x000978e0 size = 4 state = enabled type = w
        declare @ '/Users/Bluefish/Documents/textPro/AutoreleasePoolTest/AutoreleasePoolTest/ViewController.m:15'
        watchpoint spec = 'string_weak_'
        new value: 0x7a160340
    
    Watchpoint 1 hit:
    old value: 0x7a160340
    new value: 0x00000000
    

    场景3

    显式使用autoreleasepool自己主动释放池,并且将string定义在autoreleasepool{}外,例如以下

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        NSString *string = nil;
        @autoreleasepool {
            string = [NSString stringWithFormat:@"bluefish"];
            string_weak_ = string;
        }
    
        NSLog(@"viewDidLoad--string: %@", string_weak_);
    }
    

    运行结果:

    viewDidLoad--string: bluefish
    viewWillAppear--string: (null)
    viewDidAppear--string: (null)

    你可能会奇怪为什么出了autoreleasepool{}后,还是能够打印出来,[NSString stringWithFormat:@"bluefish"]没被释放掉。
    以下分析。
    [NSString stringWithFormat:@"bluefish"]创建时,被增加autoreleasepool释放池。string对其强引用,当autoreleasepool{}结束时。[NSString stringWithFormat:@"bluefish"]被释放,可是,string还在其作用域内,因此还继续对[NSString stringWithFormat:@"bluefish"]做强引用,所以不会被释放。而直到viewDidLoad{}结束,string被销毁,强引用消失,在arc下。失去了不论什么强引用的[NSString stringWithFormat:@"bluefish"]终于被释放掉。

    那假设是非自己主动释放对象呢。例如以下:

    NSString *string = nil;
        @autoreleasepool {
            string = [[NSString alloc] initWithFormat:@"%@",@"bluefish"];
            string_weak_ = string;
        }

    运行打印结果也是一样。在这里autoreleasepool{}自己主动释放池事实上起不到什么实际作用。以下分析,
    [[NSString alloc] initWithFormat:@"%@",@"bluefish"]创建时。由于其不是自己主动释放对象,所以并不会增加到autoreleasepool的自己主动释放池中,也就是他的内存管理还是由arc来处理。string对其强引用。当autoreleasepool{}结束时,释放池中没有可释放的对象,而string也还在其作用域内。所以,autoreleasepool{}什么也没做,到viewDidLoad{}结束时,string销毁,[[NSString alloc] initWithFormat:@"%@",@"bluefish"]失去不论什么强引用,终于都被释放。

    小结:在arc环境下。autoreleasepool{}相同也仅仅对自己主动释放对象做管理,当autoreleasepool{}结束时,自己主动释放池中的对象。

    但由于arc机制的存在,autoreleasepool{}的功能事实上能够当成是被弱化的,它的作用很多其它的是用于在arc环境下,控制自己主动对象的释放时机,如“场景2”。

    总结

    仅仅有自己主动释放对象会被增加离它近期的自己主动释放池中,自己主动释放池结束时,池中的对象都会被释放一次。
    局部变量。在其作用域结束后,就会被销毁(出栈)。

    普通情况下。在程序中的自己主动释放对象(非显式使用alloc new copy或者MRC下使用autorelease)会被增加到当前线程的自己主动释放池中(每一个线程都会有一个默认的自己主动释放池)。当这线程结束时,其自己主动释放池中的对象被销毁。參考“场景1”。所以。当你没显式使用autoreleasepool{}的时候,程序中的自己主动释放对象依旧是在一个隐式的自己主动释放池中(你的程序都是运行中各个线程中的。包含主线程)。

    在MRC下。是通过引用计数的概念来管理内存。一个对象一旦被创建其引用计数即为1。创建后你能够使用其它普通的对象指针变量来指向它,跟arc不同是指向它不会改变这个对象的引用计数,除非向对象发送retain或者copy(浅拷贝)计数+1,创建出来的对象,假设是通过alloc new copy创建出来的。除了自己主动释放对象最后都要向对象发送release方法。
    所以,最后,仅仅要记得一条准则,在MRC下。在autoreleasepool{}中创建的非自己主动释放对象都要调用release方法。

    而autoreleasepool{}创建的自己主动释放对象,一旦autoreleasepool结束就会被release一次。无论其之前被哪个对象引用过。

    在ARC下,内存管理以对象强弱引用来处理,一个对象一旦失去全部强引用,就会被销毁。其弱引用会被自己主动设为nil。


    在ARC下。创建出来的对象能够视为两种不同的内存处理模式。

    一种是非自己主动释放对象,其内存管理由arc来处理。

    一种是自己主动释放对象,其内存管理由自己主动释放池来出来。
    同理。在autoreleasepool{}中创建的非自己主动对象由arc来处理,自己主动释放对象由当前autoreleasepool来管理。但由于arc的环境下,无论在autoreleasepool{}中创建的非自己主动释放对象还是自己主动释放对象。最后都会被释放一次(由于autoreleasepool{}中的非自己主动释放对象会在该作用域范围结束时被arc释放一次)。


    而假设在autoreleasepool{}结束时。有作用域在该autoreleasepool{}外的变量对里面创建的对象做强引用时,该对象继续保留。

  • 相关阅读:
    训练总结
    图论--最短路--SPFA模板(能过题,真没错的模板)
    图论--最短路-- Dijkstra模板(目前见到的最好用的)
    The 2019 Asia Nanchang First Round Online Programming Contest B Fire-Fighting Hero(阅读理解)
    关于RMQ问题的四种解法
    The Preliminary Contest for ICPC Asia Xuzhou 2019 徐州网络赛 K题 center
    The Preliminary Contest for ICPC Asia Xuzhou 2019 徐州网络赛 XKC's basketball team
    The Preliminary Contest for ICPC Asia Xuzhou 2019 徐州网络赛 D Carneginon
    ZOJ 3607 Lazier Salesgirl (枚举)
    ZOJ 3605 Find the Marble(dp)
  • 原文地址:https://www.cnblogs.com/llguanli/p/8493322.html
Copyright © 2020-2023  润新知