|
|
51CTO旗下网站
|
|
移动端

引用计数

《iOS和macOS性能优化:Cocoa、Cocoa Touch、Objective-C和Swift》第7章内存:陷阱和优化技巧,本章将围绕这个话题来进行讨论。除此之外,我们将展示一些陷阱,尤其是在Objective-C 代码中经常出现的问题。本节为大家介绍引用计数。

作者:李俊阳 等译来源:电子工业出版社|2018-07-17 16:20

第7章 内存:陷阱和优化技巧

很多常见的内存优化技术都是研究如何让数据结构更为紧凑,本章将围绕这个话题来进行讨论。除此之外,我们将展示一些陷阱,尤其是在Objective-C 代码中经常出现的问题。(可以猜想一下,这些问题是否包括了对Foundation 对象的误用。)

当然,我们还需要研究一下如何避免内存泄漏、引用计数在Objective-C 和Swift 引用类型中所扮演的特殊角色,以及为什么用值传递来替代引用传递。我们还会了解Objective-C 的缓存技术及相关的API,并且还要了解将地址空间映射到实际内存的API。

下面将继续讨论:架构选择之于性能的重要性。这里将使用一个小例子,来强调不同的架构选择所带来的性能差异。最后,但也是非常重要的一点,我们将探讨并发问题及特殊的iOS 内存环境。

引用计数

即便引入了自动引用计数(Automatic Reference Counting,ARC),在所有关于Objective-C 的讨论之中,高居榜首的仍然是Apple Foundation 框架中的引用计数机制。不得不承认我有些过虑了,因为引用计数用起来很简单,同时它的性能特点也很好预测。由于引用计数的经典实现是作为库代码提供的,因此它也适用于正常的代码析取技术,从而减少相关的代码量。

由于ARC 的性能限制,所以我现在仍然在性能关键代码中使用非ARC 引用计数。我对引用计数的处理策略指定了以下约定。

1.除了在dealloc 方法内,应该要一直使用访问器;

2.对于非自动释放的get 访问器,或者使用自动访问器属性合成功能的非原子(non-atomic)属性而言,它们所有的访问器都应该自动生成;

3.始终使用便利方法来创建对象。

借助这些约定,除了在dealloc 中按部就班发送release 消息,非ARC 代码中基本不存在引用计数的相关操作。用数字可以更清晰地说明这一点:在250 KLOC(KiloLines Of Code,千行代码)中,大概会有230 次[...retain]的出现,也就是大概1000多行代码中才会出现1 个retain。这些额外的retain 大部分是历史遗留问题或者是“特殊情况”所导致的,比如说(可能过早地)优化。由于这些dealloc 方法无法自动生成,所以导致release 消息出现的频率大概是retain 的10 倍左右,但是这也仅占总代码的1%。

把手动管理内存代码的占比减少到1%,并高效地自动遵守Cocoa 内存所有权规则,使得这种小代码模式不仅几乎完全消除了内存错误,还消除了对内存的过度顾虑。不过与之相反,我所见到的内存错误大多是因为在代码中不遵循这些约定所导致的。如果在方法的中间位置看到向对象发送了retain 消息,这就非常可疑了,并且往往这就是错误的源头所在。

1%同时也是一个很小的占比,意味着将代码迁移至ARC 非常简单,因为ARC 本质就是用于垃圾收集的。不过,这也意味着这部分代码进行ARC 转换收益很小,但是不像Apple 的ARC 迁移指南中所述的那样,对1%的代码进行优化并不能带来50%的性能优化效果。

这里要注意的一个潜在误区就是属性默认的原子性修饰符,之前已经提到过,该修饰符可以在属性访问时确保线程安全。虽然实际说来,示例7.1 中这些自动生成的代码看起来很干净,但是它实际上会包含隐含的性能损耗,特别是对读访问。如果执行这段代码,会发现每个读取访问都会产生一个内存指针,从而带来性能损耗,因为原子访问器也必然包含一个自动释放访问器,这意味着对象将被添加到当前的自动释放池中。

示例7.1 测量默认的synthesized 读取访问器的内存开销

  1. #import <Foundation/Foundation.h> 
  2. #include <malloc/malloc.h> 
  3. @interface AtomicTest:NSObject  
  4. @property(retain) id myObject;  
  5. @end  
  6. @implementation AtomicTest  
  7. @end  
  8. int main(int argc, char *argv[] )  
  9. {  
  10. int count=argc>1 ? atoi(argv[1]) : 1;  
  11. [NSAutoreleasePool new];  
  12. AtomicTest* obj=[[AtomicTest new] autorelease];  
  13. obj.myObject=[NSString stringWithUTF8String:"Hello World"];  
  14. struct mstats stats=mstats();  
  15. long used_before=stats.bytes_used;  
  16. for (int i=0; i< count * 1000 * 1000; i++) {  
  17. [obj myObject];  
  18. }  
  19. stats=mstats();  
  20. long used=stats.bytes_used;  
  21. printf("n=%d memory used: %ld\n",count,used);  
  22. return 0;  

虽然所产生的性能损耗可能并不多,但是读访问实际上非常频繁,因此这种损耗在实际应用中会变得更严重,并且很多代码都会理所当然地认为读访问不会有任何损耗。将nonatomic 关键字添加到属性声明中可以将内存损耗降低到预期值0,这也可以让代码的运行速度加快10 倍。

喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书频道活动讨论群:365934973

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

数据库系统概念

本书是数据库系统方面的经典教材之一。国际上许多著名大学包括斯坦福大学、耶鲁大学、得克萨斯大学、康奈尔大学、伊利诺伊大学、印度理工学...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊