你真的懂iOS的autorelease吗?

【原】你真的懂iOS的autorelease吗?

或许这个题目起得有点太高调了,不过我只是想纠正一些童鞋对于autorelease的认识,如果能帮到几个人,那这篇文章也就值得了!当然,高手请绕道

本文主要探讨两个方面:(1)autorelease对象到底是合适被析构的?(2)OC内部是如何处理一个被autorelease掉的对象的?

(1)autorelease对象到底是何时被析构的?

这个问题说难不难,但说简单也不简单。我们还是先看一类熟悉的不能再熟悉的代码吧:

1 - (void)viewDidLoad {
2     [super viewDidLoad];
3     NSArray *localArr = [NSArray arrayWithObject:@"Weng Zilin"];//这是一个局部对象,封装了autorelease方法
4     }

请问,localArr这个局部变量何时被析构呢?很多人会回答:“出了作用域,也就是花括号之后就会被回收”。但遗憾的是,事实并非你想象的那般顺利。下面我通过几行代码向你证明,localArr出了作用于依旧活得好好的:(ARC环境下)

__weak id objTrace;
- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *localArr = [NSArray arrayWithObject:@"Weng Zilin"];//这是一个局部对象,封装了autorelease方法
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear__localArr:%@", objTrace);
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSLog(@"viewWillAppear__localArr:%@", objTrace);
}

在ARC环境下我用一个__weak类型来追踪localArr的释放时机,__weak并不会对localArr增加引用计数,因此不干扰其释放,log显示如下:

我们发现,localArr在viewWillAppear还活着,在DidAppear已经挂了。这说明了一件事:autorelease并不是根据作用域来决定释放时机的。那到底是依据什么呢?答案是:runloop。runloop不在本文讨论范围内,感兴趣的同学请自行查阅资料,传送门点这里。简单说,runloop就是iOS中的消息循环机制,当一个runloop结束时系统才会一次性清理掉被autorelease处理过的对象,其实本质上说是在本次runloop迭代结束时清理掉被本次迭代期间被放到autorelease pool中的对象的。至于何时runloop结束并没有固定的duration!

那么问题来了:iOS的这种基于runloop的内存回收策略有不方便的时候吗?我认为是显然有的。但凡事物总是有两面性的,使用autorelease的确方便,但在一定的情况下会带来性能问题。我们看个例,这个例子转载在我之前的文章

for (int i = 0; i <= 1000; i ++) {
       //1.首先我们获取到需要处理的图片资源的路径
        NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"PNG"];
        //2.将图片加载到内存中,我们使用了alloc关键字,在使用完后,可以手动快速释放掉内存
        UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];
       //3.这一步我们将图片进行了压缩,并得到一个autorelease类型实例
        self.image2 = [image imageByScalingAndCroppingForSize:CGSizeMake(480, 320)];
       //4.释放掉2步骤的内存
        [image release];
    }

上述例子看起来没有什么问题,因为一切都是按照MRC的规定做的,可以说是一种“看起来”十分规范的写法。但是主要到image2这个对象了没,赋值给image2对象的临时image对象是一个autorelease类型。实际去跑这段程序会发现,在循环1000次的条件下内存持续上升,因为那个autorelease对象并没有如我们预期般在每次for循环的花括号结束时释放掉!如果从runloop的角度考虑就显得合理了。

那么问题又来了:既然交给runloop处理不放心(runloop其实是有人类的“拖延症”的),那我们可以人工干预autorelease对象的释放时机吗?答案是,欢天喜地,可以的。上文有提到autorelease pool,这是下一个问题要解决的任务,在这里不展开,你只需要知道,一旦一个对象被autorelease,则该对象会被放到iOS的一个池:autorelease pool,其实这个pool本质上是一个stack,扔到pool中的对象等价于入栈。我们把需要及时释放掉的代码块放入我们生成的autorelease pool中,结束后清空这个自定义的pool,主动地让pool清空掉,从而达到及时释放内存的目的。以上述图片处理的例子为例,优化如下:

 1 for (int i = 0; i <= 1000; i ++) {
 2  
 3        //创建一个自动释放池
 4         NSAutoreleasePool *pool = [NSAutoreleasePool new];//也可以使用@autoreleasePool{domeSomething}的方式
 5         NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"PNG"];
 6         UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];
 7         UIImage *image2 = [image imageByScalingAndCroppingForSize:CGSizeMake(480, 320)];
 8         [image release];
 9        //将自动释放池内存释放,它会同时释放掉上面代码中产生的临时变量image2
10         [pool drain];
11     }

其中对pool的操作也可以等价地使用@autoreleasePool{domeSomeThing;}替代。以上就简要地回答了本文开始处抛出的第一个问题,小结一下就是:释放时机是基于runloop而不是作用域;通过autorelease pool手动干预释放;循环多次时当心要对autorelease进行优化。下面我们开始第二个问题的讨论

(2)一个对象被标记为autorelease后经历了怎么样的过程?

其实我认为这个问题讨论起来更有意思,因为它已经比较底层了。前面提到autorelease对象最终被放到autorelease pool中,那这个pool到底是何方神圣呢?当我们使用@autoreleasepool{}时,编译器实际上将其转化为以下代码:

void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);//当前runloop迭代结束时进行pop操作

而objc_autoreleasePoolPush与objc_autoreleasePoolPop又是什么呢?他们只是对autoreleasePoolPage的一层简单封装,下面是autoreleasePoolPage的结构,它是C++数据类型,本质是一个双向链表。next就是指向当前栈顶的下一个位置。

里面还有各种参数,不过记住这句话就行:向一个对象发送- autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置。

在文章的最后顺便提一下,在iOS中有三种常用的遍历方法:for、forin、enumerateObjectsUsingBlcok。实际使用中大家可能没有感觉到又什么区别,前面两个比较常用,最后一个是iOS特有的遍历方式,但事实上还是有区别的。block版本的遍历方式已经内嵌了@autoreleasepool{}操作,而前面两个没有,这样就意味着使用block版本的遍历方式会使app更加健壮,内存使用效率更加出色,而且,逼格更高,嘿嘿!

这篇文章的讨论就到这里,that`s all.

Reference:

http://blog.sunnyxx.com/

http://www.cnblogs.com/wengzilin/p/3301549.html

http://www.cnblogs.com/xwang/p/3547685.html

=======================================================

原创文章,转载请注明 编程小翁@博客园,邮件zilin_weng@163.com,微信Jilon,欢迎各位与我在C/C++/Objective-C/机器视觉等领域展开交流!

 =======================================================

 

posted @ 2015-03-20 13:54 编程小翁 阅读(567) 评论(0)  编辑 收藏


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: iOS内存管理版本记录如下: 1. iOS 2.0及更早版本:使用手动管理内存的方式。 2. iOS 3.0:引入了基于引用计数的自动内存管理,使用retain和release函数来增加或减少对象的引用计数。 3. iOS 5.0:引入了ARC(自动引用计数)机制,ARC会在编译时自动插入retain和release代码,减少手动管理内存的工作。 4. iOS 7.0:引入了内存诊断工具Memory Usage Report,可以监测App内存使用情况,帮助开发者优化内存管理。 5. iOS 8.0:引入了一些新的API,如NSCache和NSURLSession,使得内存管理更加方便和灵活。 6. iOS 11.0:引入了基于图片大小的UIImage渲染机制,减少了内存占用。 7. iOS 13.0:引入了叫做“Scene”的多任务环境,使得内存管理更加复杂,需要更加小心谨慎地处理内存问题。 总的来说,随着iOS版本的不断更新,内存管理的机制也在不断地完善和优化,使得iOS应用能够更加高效地使用内存,提高用户体验。 ### 回答2: iOS的内存管理是由操作系统自动管理的,在不同的版本中有所不同。 在iOS 5之前的版本中,内存管理主要依赖于手动管理引用计数(reference counting)来管理对象的生命周期。开发者需要手动调用retain和release方法来增加或减少对象的引用计数,以确保对象在不再需要时能够被正确释放。这种方式需要开发者非常谨慎地管理对象的引用,以避免内存泄漏或野指针等问题。 从iOS 5开始,iOS引入了自动引用计数(Automatic Reference Counting,ARC)的内存管理机制。ARC可以自动地插入retain、release和autorelease等方法的调用,使得开发者不再需要手动进行内存管理。开发者只需要关注对象的创建和使用,而不需要关心具体的内存管理细节。ARC减少了内存管理的工作量,提高了开发效率,并且减少了内存泄漏和野指针等问题的发生。不过,ARC并不是完全的自动化内存管理,开发者仍然需要遵循一些规则,比如避免循环引用等,以保证内存的正确释放。 随着iOS版本的不断更新,苹果不断改进和优化内存管理机制。每个新版本都带来了更好的性能和更高效的内存管理。开发者可以通过关注苹果的官方文档和开发者社区中的更新内容来了解每个版本中的具体变化和改进。 总结来说,iOS的内存管理从手动的引用计数到自动引用计数的演变,极大地简化了开发者的工作,并提高了应用的性能和稳定性。随着不断的改进和优化,iOS的内存管理会越来越高效和可靠。 ### 回答3: iOS内存管理版本记录是指苹果公司在不同版本的iOS操作系统中对于内存管理方面的改进和更新记录。随着iOS版本的不断迭代,苹果在内存管理方面进行了一系列的优化和改进,以提高系统的稳定性和性能。 首先,在早期的iOS版本中,苹果采用了手动内存管理的方式,即开发人员需要手动创建和释放内存,容易出现内存泄漏和内存溢出等问题。为了解决这些问题,苹果在iOS5版本中引入了自动引用计数(ARC)机制。ARC机制能够通过编译器自动生成内存管理代码,避免了手动管理内存带来的问题。 其次,iOS6版本引入了内存分页机制。这个机制能够将应用程序内存分成不同的页,将不常用的页置于闲置列表中,从而释放出更多的内存空间。这些闲置列表中的页能够在需要时快速恢复到内存中,减少了内存压力。 此外,iOS7版本中进一步提升了内存管理的能力。苹果在这个版本中引入了内存压缩技术,将内存中的数据进行压缩,从而提高了内存利用率。此外,iOS7还引入了资源清理功能,可以自动清理不再使用的资源,释放内存空间。 最后,在iOS13版本中,苹果进一步改进了内存管理策略。该版本中引入了后台内存优化功能,能够自动优化应用在后台运行时的内存占用,减少了后台应用对于系统内存的占用和影响。 综上所述,iOS内存管理版本记录反映了苹果在不同版本的iOS操作系统中对于内存管理方面的改进和优化。这些改进和优化使得iOS系统更加稳定和高效,并且提升了应用程序的性能和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值