该教程是讨论IOS平台上内存管理规则之外的一些特殊情况,我相信大部分的开发人员可能都没有觉察到。

我们先普及一下Objectivie-C中的内存管理的基本知识,如果你已经比较熟悉了,可以直接跳过该节。Objective-C使用的是引用计数(Reference Counting),引用计数就是对象用一个变量来保存有几个地方(类、方法等)在使用它。当一个对象被创造出来时,它的引用计数(下面我们用retainCount来表示这个值)为1,在应用程序运行的过程中,可能有很多地方都用到了这个对象,凡是用到这个对象时,就将它的retainCount加1,当不用了时,再将其retainCount减1,当对象的retainCount为0时,表示没有人在用这个对象了,系统就会释放这个对象所占用的内存。

在Objective-C中,关于基于引用计数的内存管理,其实你只需要掌握三条最基本的规则:
  • 当你使用new、alloc或copy方法创建一个对象时,对象的引用计数值为1.当不再使用该对象时,你要负责向该对象发送一条release或autorelease消息。这样该对象会在其使用寿命结束时被销毁。
  • 当你通过其它方法获得一个对象时,则假设该对象的保留计数值为1, 而且已经被设置为自动释放,你不需要执行任何操作来确保该对象被清理。但是如果你打算在一段时间内拥有该对象,则需要保留(引用计数加1)它,并且在操作完成时释放它(引用计数减1)。
  • 如果你保留了某个对象,你需要最终释放或者自动释放该对象,必须保持保留方法(retain)和释放方法(release/autorelease)的使用次数相等。
    只要你理解了这三条规则基本就足够了。但是要理解下面所讲的一些例外情况,仅有这些知识就不够了。下面就让我们开始这段探索之旅吧。
使用Xcode使用“IOS”-“Application”-“Single View Application”模板创建一个名为MemoryTest的工程。在ViewController.m文件的viewDidLoad方法中键入下面的代码:
  1. NSString *emptyStr = [NSString new];
  2. NSLog(@"emptyStr retainCount: %u", emptyStr.retainCount);
先想想你认为输出结果是什么?然后看一下运行后Console的输出是什么?我这边的结果是4294967295,一个很大的数,实际上这个数是UINT_MAX,就是无符号整型的最大值。你原先认为它应该输出1对吗?为什么会这样呢?带着这个疑问在看下面的代码,仍旧将其放入viewDidLoad方法中:
 
  1. NSString *emptyStr1 = [NSString new];
  2. NSString *emptyStr2 = [NSString new];
  3. NSLog(@"emptyStr1 address: %p", emptyStr1);
  4. NSLog(@"emptyStr2 address: %p", emptyStr2);
  5. NSLog(@"emptyStr1 retainCount: %u", emptyStr1.retainCount);
  6. NSLog(@"emptyStr2 retainCount: %u", emptyStr2.retainCount);
这段代码创建了两个新的空字符串对象,接着输出这两个对象的在内存中的地址和它们的引用计数值。运行一下,查看一下结果,我这边的结果是:

这两个对象的地址竟然一样,这出离我们原先的认识对吗?new方法两次创建的对象竟然一样,这在c++中是绝不可能的。但这是Objective-C,编译器在底层做了一些我们看不见的工作。很显然这两个空字符串对象指针指向的是同一个对象。Objective-C为什么会这么处理呢?这是因为NSString类型的不可变性,就是这种类型的对象一旦创建,就不能改变(增加或删除其中的字符),如果你希望改变对象,那就用NSMutableString类型。NSString的不可变性使得空字符串对象一旦创建,就不能改变,永远是空字符串,而所有的空字符串都是一样的。所以出于效率的考量,Objective-C编译器在底层就让所有创建的空字符串指向内存中的同一个空字符串。并且这个空字符串对象是不可release掉的,因此它的引用计数值就为UINT_MAX,表示这个对象是不可release的,那你可能会问,我如果release UINT_MAX次,是不是就释放掉了,不是的,实际上你向这个对象发送release消息是没有任何效果的。
 我们换非空字符串试试,输入下面的代码:
  1. NSString *nonEmptyStr1 = @"Hello";
  2. NSString *nonEmptyStr2 = [[NSString alloc] initWithString:@"Hello"];
  3. NSString *nonEmptyStr3 = [[NSString alloc] initWithFormat:@"%@", @"Hello"];
  4. NSLog(@"nonEmptyStr1 address: %p", nonEmptyStr1);
  5. NSLog(@"nonEmptyStr2 address: %p", nonEmptyStr2);
  6. NSLog(@"nonEmptyStr3 address: %p", nonEmptyStr3);
  7. NSLog(@"nonEmptyStr1 retainCount: %u", nonEmptyStr1.retainCount);
  8. NSLog(@"nonEmptyStr2 retainCount: %u", nonEmptyStr2.retainCount);
  9. NSLog(@"nonEmptyStr3 retainCount: %u", nonEmptyStr3.retainCount);
下面是在机器上运行结果:

前两个指针仍然是指向同一个对象,原因上面已经解释了。但是第三个不一样,你可以从NSString的不可变性和对象的初始化方式的不同出发想想原因,相信你很快就可以得出结论的。
在Objective-C中不唯NSString是不可变对象,还有NSArray和NSDictionary。同样你可以试试下面的代码:
 
  1. NSArray *emptyArray1 = [[NSArray alloc] init];
  2. NSArray *emptyArray2 = [[NSArray alloc] init];
  3. NSArray *emptyArray3 = [[NSArray alloc] initWithArray:emptyArray1];
  4. NSLog(@"emptyArray1 address: %p", emptyArray1);
  5. NSLog(@"emptyArray2 address: %p", emptyArray2);
  6. NSLog(@"emptyArray3 address: %p", emptyArray3);
  7. NSLog(@"emptyArray1 retainCount: %d", emptyArray1.retainCount);
  8. NSLog(@"emptyArray2 retainCount: %d", emptyArray2.retainCount);
  9. NSLog(@"emptyArray3 retainCount: %d", emptyArray3.retainCount);
  10. NSArray *nonEmptyArray1 = [[NSArray alloc] initWithObjects:@"1", @"2", nil];
  11. NSArray *nonEmptyArray2 = [[NSArray alloc] initWithObjects:@"1", @"2", nil];
  12. NSLog(@"nonEmptyArray1 address: %p", nonEmptyArray1);
  13. NSLog(@"nonEmptyArray2 address: %p", nonEmptyArray2);
  14. NSLog(@"nonEmptyArray1 retainCount: %d", nonEmptyArray1.retainCount);
  15. NSLog(@"nonEmptyArray2 retainCount: %d", nonEmptyArray2.retainCount);
  16. NSDictionary *emptyDict1 = [[NSDictionary alloc] init];
  17. NSDictionary *emptyDict2 = [[NSDictionary alloc] init];
  18. NSLog(@"emptyDict1 address: %p", emptyDict1);
  19. NSLog(@"emptyDict2 address: %p", emptyDict2);
  20. NSLog(@"emptyDict1 retainCount: %d", emptyDict1.retainCount);
  21. NSLog(@"emptyDict2 retainCount: %d", emptyDict2.retainCount);
通过运行结果来进一步体会一下Objective-C编译器底层的工作机理。
按照上面提到过的三条内存管理规则,你new、init、copy得到一个对象,你就必须负责release掉它,但实际上前面提到过的这些语句是个例外:
 
  1. NSString *s1 = [NSString new];
  2. NSString *s2 = [NSString alloc] initWithString:@"Hello"];
  3. NSArray *a = [NSArray alloc] init];
  4. NSDictionary *dict = [NSDictionary alloc] init];
就算你不调用release或autorelease方法释放也不会造成内存泄漏,你可以用Instruments检测一下看看是否有内存泄漏。但是虽然如此,我仍强烈建议你按照内存管理三规则来处理。一致的规则不容易让人迷惑,尤其是对阅读你代码的人。
 
本教程的工程文件:MemoryTest.zip
 
本文作者:安海林,软件工程师,诺基亚北京研究院。他的格言是:学问深时意气平!

转载于:https://blog.51cto.com/hailinan/877887

Objective-C中内存管理的一些特例相关推荐

  1. Python中内存管理的问题

    Python中内存管理的问题 pyqtgraph实时显示占用内存 删除变量释放内存 后记 我的环境是python3+SublimeText.python是解释型语言,平常对内存关注得不多. pyqtg ...

  2. Linux中内存管理详解

    Linux中内存管理 内存管理的主要工作就是对物理内存进行组织,然后对物理内存的分配和回收.但是Linux引入了虚拟地址的概念. 虚拟地址的作用 如果用户进程直接操作物理地址会有以下的坏处: 1. 用 ...

  3. Linux内核中内存管理相关配置项的详细解析3

    接前一篇文章:Linux内核中内存管理相关配置项的详细解析2 5. 2:1 compression allocator (zbud) 对应配置变量为:CONFIG_ZBUD. 此项默认为选中(如果前一 ...

  4. python中内存管理机制一共分为多少层_python 内存管理机制

    内存管理机制 ​python中万物皆对象,python的存储问题是对象的存储问题,并且对于每个对象,python会分配一块内存空间去存储它 ​Python的内存管理机制:引入计数.垃圾回收.内存池机制 ...

  5. (转载)深入理解Linux中内存管理---分段与分页简介

    首先,必须要阐述一下这篇文章的主题是Linux内存管理中的分段和分页技术. 来回顾一下历史,在早期的计算机中,程序是直接运行在物理内存上的.换句话说,就是程序在运行的过程中访问的都是物理地址.如果这个 ...

  6. 【iOS系列】-iOS中内存管理

    iOS中创建对象的步骤: 1,分配内存空间,存储对象 2,初始化成员变量 3,返回对象的指针地址 第一:非ARC机制: 1,对象在创建完成的同时,内部会自动创建一个引用计数器,是系统用来判断是否回收对 ...

  7. iOS中内存管理方案

    系统提供的有不同的内存管理方案,大致有如下三种: TaggedPointer (对于一些小对象,比如说NSNumber,NSString等采用此种方案) NONPOINTER_ISA (64位架构下i ...

  8. linux中内存管理方法的总结,Linux系统内存总结.docx

    Linux内存是后台开发人员,需要深入了解的计算机资源.合理的使 用内存,有助于提升机器的性能和稳定性.本文主要介绍Linux内存组织 结构和页面布局,内存碎片产生原因和优化算法,Linux内核儿种内 ...

  9. iOS中内存管理的问题——堆和栈

    计算机系统中,运行的应用程序的数据都是保存在内存中的,不同类型的数据,保存的内存区域不同: 1)栈区(stack)由编译器自动分配并释放,一般保存函数的参数值.局部变量 2)堆区(heap)由程序员分 ...

  10. CUDA编程中内存管理机制

    GPU设备端存储器的主要分类和特点: 大小: 全局(Global)和纹理(Texture)内存:大小受RAM大小的限制. 本地(local)内存:每个线程限制在16KB 共享内存:最大16kB 常量内 ...

最新文章

  1. 两张照片重叠处半透明_手机可以“抛起来”拍照,给你的照片换个角度
  2. 三维感知,这些干货足够了!(自动驾驶/三维重建/SLAM/点云/标定/深度估计/3D检测)...
  3. GNU Wget 命令及其参数说明
  4. 乡村要振兴,快递先进村?
  5. 在CentOS上使用Jexus托管运行 ZKEACMS
  6. Java解决循环注入问题
  7. Mac更新之后使用终端提示:The default interactive shell is now zsh.
  8. B - 简单暴力(计算今年第几天)
  9. 视频码率[百科词条]
  10. 【论文笔记】基于LSTM的问答对排序
  11. cloudsim的安装和配置
  12. linux服务之NTP及chrony时间同步
  13. 交换局域网(链路层+以太网+交换机)
  14. 第五天 面向对象软件分析与设计
  15. VFP开眼看世界的第一眼,就是学会真正的BS开发,走错一步费三年
  16. 路由器/交换机/服务器的分类
  17. 在mac上使用vscode创建第一个Python项目
  18. 网络攻击还是网络战争?
  19. Cadence: 各软件业务
  20. 微信小程序ios端唤醒不了拨打电话或者部分电话拨打不了解决方案

热门文章

  1. 你还不懂云计算吗?资深互联网老大详细讲解云计算的应用
  2. LOJ2874 JOISC2014 历史研究 分块、莫队
  3. F5 root密码恢复
  4. vue系列---identify(生成图片验证码)插件
  5. fragment--总结
  6. objective-C nil,Nil,NULL 和NSNull的小结
  7. C#读写XML的两种一般方式
  8. 129 MySQL数据类型(重要)
  9. Java开发笔记(一百二十八)Swing的图标
  10. 关于STM32定时器使用的一个注意事项(以此为前车之鉴,重要!)