iOS 自动释放池相关

autoreleasepool自动释放池

自动释放池始于MRC时代,主要是用于 自动 对 释放池内 对象 进行引用计数-1的操作,即自动执行release方法。

在MRC中使用autoreleasepool必须在代码块内部手动为对象调用autorelease把对象加入到的自动释放池,系统会自动在代码块结束后,对加入自动释放池中的对象发送一个release消息。无需手动调用release

int main(int argc, const char * argv[])
{
Person *father =  [[Person alloc] init];//引用计数为1
    @autoreleasepool {//这里创建自动释放池
        int a = 10; // a在栈,10在常量区
        int b = 20; //b在栈,20在常量区
        // p : 栈
        // [[Person alloc] init]创建的对象(引用计数器==1) : 在堆
        Person *p = [[Person alloc] init];
       //MRC下需要手动让对象加入自动释放池
       [p autorelease];

        Person *pFather = father;
        [father retain];//father指针指向的对象被pFather引用,在MRC下需要手动让被引用对象引用计数+1

        NSLog(@"pFather对象内存地址:%p,pFather的引用计数:%zd",pFather,[pFather retainCount]);
        NSLog(@"father对象内存地址:%p,father的引用计数:%zd",father,[father retainCount]);

    }//这里释放 自动释放池
    // 当autoreleasepool内部代码块执行完毕后上面代码块后, 栈里面的变量a、b、p 都会被回收
    // 但是堆里面的Person对象还会留在内存中,因为它是计数器依然是1。当autoreleasepool代码块执行完毕后,会对释放池内部的所有对象执行一个release消息。如果发送release消息后,对象引用计数为0了,那么就会被系统回收。

NSLog(@"father对象内存地址:%p,father的引用计数:%zd",father,[father retainCount]);
    return 0;
}

在ARC中对@autoreleasepool的使用相比MRC不太多。主要用于一些大内存消耗对象的重复创建时,保证内存处于比较优越的状态。常用于创建对象较多的for循环中。在ARC下不要手动的为@autoreleasepool代码块内部对象添加autorelease,ARC下自动的把@autoreleasepool代码块中创建的对象加入了自动释放池中。

    for (int i = 0; i < 10000000; i++)
    {
        @autoreleasepool{
            NSMutableArray *array = [NSMutableArray new];
            NSMutableDictionary *dic = [NSMutableDictionary new];
            NSMutableArray *array1 = [NSMutableArray new];
            NSMutableDictionary *dic1 = [NSMutableDictionary new];
            NSMutableArray *array2 = [NSMutableArray new];
            NSMutableDictionary *dic2 = [NSMutableDictionary new];
            NSData *data = UIImageJPEGRepresentation([UIImage imageNamed:@"testimage"], 1);
            NSError *error;
            NSURL *url = [NSURL URLWithString:@"www.baidu.com"];
            NSString *fileContents = [NSString stringWithContentsOfURL:url
                                                              encoding:NSUTF8StringEncoding
                                                                 error:&error];
        }
    }

image

可以从上图看出,使用autoreleasepool时,内存一直保持稳定状态,上次几乎没浮动

image

因为没有使用 autoreleasepool,随着for循环一直执行下去,内存一直在上升。

autorelease对象释放的时机
1.系统自行创建的@autoreleasepool释放时机
线程与Runloop是一对一关系,主线程中会自动创建Runloop,而子线程需要自行调用Runloop来让子线程自动创建Runloop。当@autoreleasepool加入到某个线程时,该线程的Runloop会
借用runloop的Autorelease对象释放的背后的解释(ARC环境下)

image

图中第1步 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前

图中第6步 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

图中第10 Observer 监视事件是exit(即讲退出runloop),其回调内会调用 _objc_autoreleasePoolpop() 释放自动释放池。

从上面就能看出,Runloop中系统自动创建的@autoreleasepool是在准备进入休眠状态才被销毁的。所以在ARC下,在线程中的临时对象是在当前线程的Runloop进入休眠或者退出loop或者退出线程时被执行release的。

2.自己创建的@autoreleasepool

@autoreleasepool
{//这个{开始创建的自动释放池,这里开始内部的对象自动加入autorelease

}//这个}开始,不必再对 对象加入autorelease,自动释放池被销毁。

从上面就能看出,自行创建的@autoreleasepool,是在}后被释放的,而其中的autorelease对象,也是在这个时候自动执行的release操作。

1, 2 两点可以看出,虽然autorelease对象释放的时机并不都是在代码块结束后就释放。但是他们有一个共同特性,那就是必定是在@autoreleasepool被销毁时,释放的。 所以要清楚autorelease对象什么时候被释放,只需要搞清楚@autoreleasepool什么时候被销毁即可

主线程中既有系统创建的@autoreleasepool也有开发者自行创建的@autoreleasepool。那么他的释放顺序是怎样的呢?
因为@autoreleasepool是以栈的形式存储的,按照先进后出的规则释放栈中每个@autoreleasepool。主线程的@autoreleasepool是在Runloop一开始就创建了所以,它必然是栈最里面的,而自行创建的@autoreleasepool是在Runloop运行中创建的,所以在栈上面一点。按照栈的规则,@autoreleasepool是先释放自行创建的@autoreleasepool,在释放系统创建的。


NSString 单独说

为什么要单独说NSString呢,因为NSString在内存上与其他类型存在很大的不同

    //该代码是在MRC环境下测试用  

    NSString *str1 = @"123456789";//用@""方法创建一个  固定长度为9的字符串
    NSString *str2 = @"1234567890";//用@""方法创建一个  固定长度为10的字符串

    NSString *str3 = [NSString stringWithFormat:@"234567890"];//用stringWithFormat方法创建一个  固定长度为9的字符串
    NSString *str4 = [NSString stringWithFormat:@"2345678901"];//用stringWithFormat方法创建一个  固定长度为10的字符串

    NSString *str5 = [[NSString alloc] initWithString:@"345678901"];//用initWithString方法创建一个  固定长度为9的字符串
    NSString *str6 = [[NSString alloc] initWithString:@"3456789012"];//用initWithString方法创建一个  固定长度为9的字符串

    NSString *str7 = [[NSString alloc] initWithFormat:@"456789012"];//用initWithFormat方法创建一个  固定长度为9的字符串
    NSString *str8 = [[NSString alloc] initWithFormat:@"4567890123"];//用initWithFormat方法创建一个  固定长度为9的字符串

    NSString *str9 = [NSString stringWithFormat:@"1234567890"];//用stringWithFormat方法创建一个  固定长度为10的字符串并与str2字符串一样的字符串
    NSString *str10 = [[NSString alloc] initWithString:@"1234567890"];//用initWithString方法创建一个  固定长度为10的字符串并与str2字符串一样的字符串

    NSLog(@"str1 用@"" 的retainCount为:%ld \n  对象内地地址:%p",[str1 retainCount],str1);
    NSLog(@"str2 用@"" 的retainCount为:%ld \n  对象内地地址:%p",[str2 retainCount],str2);
    NSLog(@"-----------------------------------------------------------------");
    NSLog(@"str3 用stringWithFormat 的retainCount为:%ld \n  对象内地地址:%p",[str3 retainCount],str3);
    NSLog(@"str4 用stringWithFormat 的retainCount为:%ld \n  对象内地地址:%p",[str4 retainCount],str4);
    NSLog(@"-----------------------------------------------------------------");
    NSLog(@"str5 用initWithString 的retainCount为:%ld \n  对象内地地址:%p",[str5 retainCount],str5);
    NSLog(@"str6 用initWithString 的retainCount为:%ld \n  对象内地地址:%p",[str6 retainCount],str6);
    NSLog(@"-----------------------------------------------------------------");
    NSLog(@"str7 用initWithFormat 的retainCount为:%ld \n  对象内地地址:%p",[str7 retainCount],str7);
    NSLog(@"str8 用initWithFormat 的retainCount为:%ld \n  对象内地地址:%p",[str8 retainCount],str8);
    NSLog(@"-----------------------------------------------------------------");
    NSLog(@"使用lu看一下 str1 的retainCount为:%lu \n  对象内地地址:%p",[str1 retainCount],str1);
    NSLog(@"使用lu看一下 str4 的retainCount为:%lu \n  对象内地地址:%p",[str4 retainCount],str4);
    NSLog(@"-----------------------------------------------------------------");
    NSLog(@"str9  字符串与str2一样 的retainCount为:%lu \n  对象内地地址:%p",[str9 retainCount],str9);
    NSLog(@"str10  字符串与str2一样 的retainCount为:%lu \n  对象内地地址:%p",[str10 retainCount],str10);

输出结果

image

1.为什么用 @"", stringWithFormat, initWithString, initWithFormat四种方式。

因为方式不同,创建的对象所存在内存区域不同。你会发现str2 与 str9 字符串内容一样。为什么对象地址一个是0x1053674f8 另一个是0x604000033580。正常情况下,字符串内容一样,应该取的是同一个内存地址。就像str2与str10一样。虽然创建的方法不一样,但是字符串一样,内存地址就是一样的。 这就是区别。 @“”与initWithString方法创建的字符串于stringWithFormat、initWithFormat创建的字符串所在的内存区域不同。

2.为什么要区分长度9 和 长度10的字符串?

因为字符串长度不一样,字符串所在内存区域不同,retainCount也不同。从上面结果中可以看出,str3和str4是同一种方式创建的字符串,但一个内存是0xa287dcaecc2ac5d9一个是0x6040000339a0。因为前者是在五大区域之外的内存区,而后者在堆中。

由上面连2点结合可知,由initWithString和stringWithString创建的NSString对象,不管字符串的内容和长度怎么变化,该字符串对象始终是存储在常量区的,引用计数为-1;从用%lu打印来看initWithString和stringWithString创建的字符串retainCount是无符号长整型的最大值。所以可以说他们没有引用计数这个概念

而由initWithFormat和stringWithFormat创建的对象,如果字符串内容是非汉字的,那么当字符串长度小于10个时,该字符串存储区域在五大区域之外,且随着字符串长度的变化,存储地址会有很大变化。当字符串长度超过10个以后,该字符串在堆中,与正常的OC对象一样。这里为什么要说非汉字呢,因为如果字符串内容是汉字,不管字符串的内容和长度怎么变化,该字符串都是在堆中,与正常OC对象一样。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,093评论 1 32
  • 自动释放池(Autorelease Pool) 先看一个例子: 这段程序会输出什么呢? 分析:我们通过 [NSSt...
    Pandakingli阅读 2,915评论 0 3
  • 面向对象的三大特征,并作简单的介绍。 面向对象的三个基本特征是:封装、继承、多态。 1.封装是面向对象的特征之一,...
    xiny123阅读 1,426评论 0 6
  • 9:30到11点 处理顾客的一些小问题。更新 顾客信息,开卡的和没开卡的,签合同和没签的。 12:00到3点 观看...
    小慧Fineyoga阅读 86评论 0 0
  • iOS | 面试知识整理 - 多 线 程 (六) 1.什么是多线程? 多线程是指实现多个线程并发执行的技术,进而提...
    d76d0c9d2b04阅读 467评论 0 1