1、OC 基本内存管理模型

1.1 自动垃圾收集

  • 在 OC 2.0 中,有一种称为垃圾收集的内存管理形式。通过垃圾收集,系统能够自动监测对象是否拥有其他的对象,当程序执行需要空间的时候,不再被引用的对象会自动释放。iOS 运行环境并不支持垃圾收集,在这个平台开发程序时并没有这方面的选项。在 OS X 10.8 中垃圾收集已不再推荐使用。

1.2 自动释放池

  • 自动释放池(autoreleasepool)的机制是它使得应用在创建新对象时,系统能够有效的管理应用所使用的内存。自动释放池可以追踪需要延时一些时间释放的对象。

  • OC 对象只需要发送一条 autorelease 消息,就会把这个对象添加到最近的自动释放池中。autorelease 实际上只是把对 release 的调用延迟了,对于每一次 autorelease,系统只是把该对象放入了最近的 autoreleasepool 中。当执行到 autoreleasepool 块的末尾时,系统会释放自动释放池,这将影响到所有发送过 autorelease 消息并添加到自动释放池中的对象,系统会对池中的每个对象发送 release 消息,当这些对象的引用计数减到 0 时,会发出 dealloc 消息,并且他们的内存会被释放。

  • 自动释放池并不包含实际的对象,而是只包含对象的引用。

      1. 作用:
      • 自动释放对象的。
      • 所有 "autorelease" 的对象,在出了作用域之后,会被自动添加到 "最近创建的" 自动释放池中。
      • 在自动释放池被销毁或者耗尽的时候,会向池中所有对象发送 release 消息,释放池中对象。
      • 自动释放池,在 ARC & MRC 程序中,同样有效。
      1. 创建销毁时间:
      • 创建:运行循环检测到事件后,就会创建自动释放池。

      • 销毁:一次完整的运行循环结束之前,会销毁。

      1. 自动释放池的 创建:
      • iOS 5.0 以后:

        1. // 在 ARC 下只能使用该方法创建
        2. // 自动释放池的标记,表示每一次运行循环监听到系统事件的时候,会自动创建自动释放池
        3. @autoreleasepool {
        4. // 用户代码
        5. }
      • iOS 5.0 以前:

        1. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        2. // 用户代码
        3. [pool release]; // 或 :[pool drain];
      1. 自动释放池的 使用:
      • MRC

        1. @autoreleasepool {
        2. // 该 stu 对象不需要再 release 了,给对象添加延迟释放的标记,autorelease 作用就是延迟释放
        3. Student *stu = [[[Student alloc] init] autorelease];
        4. }
      • ARC

        1. @autoreleasepool {
        2. // 该 stu 对象不需要再 release 了,给对象添加延迟释放的标记,在 ARC 中系统会自动添加 autorelease
        3. Student *stu = [[Student alloc] init];
        4. }
      1. 自动释放池的 疑问:
      • 问题:在 iPhone 项目中,main() 中有一个默认的 autoreleasepool,程序开始时创建,程序退出时销毁,按照对 autoreleasepool 的理解,岂不是 autoreleasepool 里的所有对象在程序退出时才 release,这样跟内存泄漏有什么区别?

      • 答案:对于每一个 Runloop(运行循环)系统都会隐式的创建一个 autoreleasepool,并把创建好的 pool 放在栈顶,所有的 pool 会构成一个栈式结构。在每一个 Runloop 结束时,当前栈顶的 pool 会被销毁,这样这个 pool 里的每个对象会被执行 release 操作。

      1. 自动释放池的 注意:
      • 在 ARC 下不能使用 [[NSAutoreleasePool alloc] init],而应当使用 @autoreleasepool。
      • 不要把大量循环操作放在同一个自动释放池中,这样会造成内存峰值的上升。
      • 尽量避免大内存使用该方法,对于这种延迟释放机制,还是尽量少用,而是使用 release 操作。
      • SDK 中一般利用静态方法(类方法)创建并返回的对象都是已经 autorelease 的,不需要再进行 release 操作。
      • 如果使用 MRC 开发,所有返回 instancetype 的类方法,返回的对象都是 autorelease 的。

1.3 MRC 手动引用计数

  • MRC(Mannul Reference Counting):手动引用计数。

    1. 范围:
    • 任何继承了 NSObject 类的对象,对基本数据类型无效。
    1. 原理:
    • 每个对象内部都保存了一个与之相关的整数,称之为引用计数器。

    • 当使用 alloc、new 或者 copy 等创建一个对象时,对象的引用计数器被设置为 1。

    • 给对象发送一条 retain 消息,可以使引用计数器值 +1。

    • 给对象发送一条 release 消息,可以使引用计数器值 -1。

    • 当一个对象的引用计数器值为 0 时,那么它将被销毁,其占用的内存被系统回收,OC 会自动向对象发送一条 dealloc 消息。

    • 一般会重写 dealloc 方法,在这里释放相关资源,一定不要直接调用 dealloc 方法。

    • 可以给对象发送 retainCount 消息获取当前的引用计数值。

    1. 原则:
    • 谁创建,谁释放。

    • 谁 retain ,谁 release。只要你调用了 retain ,无论这个对象是如何生成的,你都要调用 release。

    • 并不是所有新创建的对象都会被添加到自动释放池中。实际上,任何由以 alloc、 copy 、mutableCopy 和 new 为前缀的方法创建的对象都不会被自动释放。在这种情况下可以说你拥有这个对象。要释放这些对象需要主动发送 release 消息,或者给对象发送 autorelease 消息将对象加入到自动释放池中。

    • 当把一个对象塞进数组中时,这个对象的引用计数器值会加 +1,当这个数组被销毁时,数组会对其中的所有对象做一次 release 操作,使其引用计数器值 -1。

    1. 析构方法:
    • 对象销毁前系统会自动调用此方法。

      1. - (void)dealloc {
      2. // 用户代码
      3. // MRC 下一定要调用该方法,ARC 下不允许手动操作,不能写该句
      4. [super dealloc];
      5. }
    1. assign 修饰符:
    • assign 修饰符,对对象不做任何操作,只是简单的记录地址,对象释放后,地址仍然保留,在 MRC 开发中,因此野指针错误非常频繁。
    • 在 MRC 的中,没有 weak 修饰符,只有 assign 修饰符。

1.4 ARC 自动引用计数

  • ARC(Automatic Reference Counting):自动引用计数。

    1. 强引用 strong:
    • 通常所有对象的指针变量都是强变量。也就是说,将对象的引用赋值给变量使对象自动保持。然后,旧对象的引用会在赋值前被释放。最终,强变量默认会被初始化为零。无论他是实例变量、局部变量还是全局变量,这都成立。

    • 在 ARC 中所有的对象变量默认都是强变量(但仍然可以使用关键字 __strong 修饰)。属性变量默认不是强变量,需要使用 strong 修饰。

    • 在 OC 中对象默认都是 strong。viewController 对根视图是强引用,view addSubviews 方法是向数组中添加子视图,数组会对子视图强引用。

    • 强引用有时会造成引用循环,无法释放对象,在程序中可以使用析构方法判断是否产生了引用循环。

      1. @implementation ViewController
      2. // 在 Block 中调用 self 容易产生引用循环
      3. [[QWebImageManager sharedManager] downloadImage:self.urlStr completion:^(UIImage *image) {
      4. self.image = image;
      5. }];
      6. @end
      1. // 判断是否存在循环应用,无法释放时即存在引用循环
      2. - (void)dealloc {
      3. NSLog(@"成功退出");
      4. }
    1. 弱引用 weak:
    • 有时候需要为对象建立两者之间的关系,每个对象需要引用到其他的对象。当两个对象都持有彼此的强引用时,将会产生循环保持,如果对象仍然有引用,系统将不可能销毁这个对象。如果两个对象都强引用到彼此,这样就不可以被销毁。解决这个问题可以通过其他类型的对象变量,并允许使用不同类型的引用。这种引用被称为弱引用,能够建立在两个对象之间。

    • 当声明一个弱变量时,系统会追踪复制给这个变量的引用。当引用的对象释放时,弱变量会被自动设置为 nil,这也避免了无意间给这个变量发送消息引起的崩溃。因为 OC 中给 nil 对象发送的任何消息不会有反应。

    • 可以使用关键字 __weak 声明一个弱变量,或者为属性指定 weak 特性。如:

      1. @implementation ViewController
      2. // 弱引用 self,typeof(self) 等价于 ViewController
      3. __weak typeof(self) weakSelf = self;
      4. [[QWebImageManager sharedManager] downloadImage:self.urlStr completion:^(UIImage *image) {
      5. weakSelf.image = image;
      6. }];
      7. @end
    • 苹果从 StoryBoard/Xib 拖线默认是 weak。设置代理属性(delegate)时一般使用 weak 属性。

    • weak 是 ARC 专有的,如果对象没有被其他任何对象做强引用,会被立即释放。

    • weak 会自动判断对象是否为 nil ,因此效率会变差。

    • weak 安全性很好。一旦没有强引用,自动将地址设置为 nil,OC 中可以向 nil 发送任何消息都不会报错。

1.5 ARC 与 MRC 编译环境的设置

    1. 对整个工程做修改:
    • 在工程的 TARGETS => Build Setting 的搜索框中输入 auto => Apple LLVM 8.0 - Language - Objective C => Objective-C Automatic Reference Counting 设置为:

      • YES : 自动引用计数 ARC
      • NO : 手动引用计数 MRC

    1. 对部分文件做修改(混编):
    • 在使用 MRC 项目中,用 “使用 ARC” 的类库时,在 TARGETS => Build Phases => Compile Sources 中对类库的所有 .m 文件添加 “-fobjc-arc”

    • 在使用 ARC 项目中,用 “使用 MRC” 的类库时,在 TARGETS => Build Phases => Compile Sources 中对类库的所有 .m 文件添加 “-fno-objc-arc”

2、MRC 中 assign 的使用

  1. // Person.h
  2. + (instancetype)person;
  3. // Person.m
  4. + (instancetype)person {
  5. // autorelease 谁申请,谁释放,作用就是延迟释放,给对象添加延迟释放的标记
  6. Person *p = [[[Person alloc] init] autorelease];
  7. return p;
  8. }
  9. // ViewController.m
  10. // 声明 assign 对象
  11. @property (nonatomic, assign) Person *p;
  12. // alloc/init 初始化方式
  13. /*
  14. MRC 中,assign 修饰符号,对对象不做任何操作,只是简单的记录地址,对象出了作用域后才会被释放
  15. */
  16. self.p = [[Person alloc] init];
  17. // 类方法初始话方式
  18. /*
  19. MRC 中,所有返回 instancetype 的系统类方法,返回的对象都是 autorelease 的
  20. */
  21. self.p = [Person person];

3、ARC 中 weak 的使用

  1. // Person.h
  2. + (instancetype)person;
  3. // Person.m
  4. + (instancetype)person {
  5. // 自动添加 autorelease,作用就是延迟释放,给对象添加延迟释放的标记
  6. Person *p = [[Person alloc] init];
  7. return p;
  8. }
  9. // ViewController.m
  10. // 声明 weak 对象
  11. @property (nonatomic, weak) Person *p;
  12. // alloc/init 初始化方式
  13. /*
  14. ARC 中,如果给 weak 做 alloc/init 的初始话,Xcode 会提示,如果对象创建后没有被其他任何对象强引用,会被立即释放
  15. */
  16. // 没有对 alloc 的对象做强引用,会被立即释放,使用 strong 则不会被立即释放
  17. self.p = [[Person alloc] init];
  18. // 对 alloc 的对象做强引用,不会被立即释放,出了作用于后会被释放
  19. NSString *str = [[[Person alloc] init] description];
  20. // 类方法初始话方式
  21. /*
  22. 对于 Xcode 编译器而言,只要是类方法,就不会提示。结果之所以为 null,是因为 weak 创建后没有被其他任何对象强引用,
  23. 会被立即释放,跟 autorelease 没有关系
  24. */
  25. // 没有对创建的对象做强引用,会被立即释放,使用 strong 则不会被立即释放
  26. self.p = [Person person];
  27. // 对创建的对象做强引用,不会被立即释放,出了作用于后会被释放
  28. NSString *str = [[Person person] description];
  • weak 的内部实现原理

    • weak 变量在引用计数为 0 时,会被自动设置成 nil,这个特性是如何实现的?

    • 在 Friday QA 上,有一期专门介绍 weak 的实现原理。https://mikeash.com/pyblog/friday-qa-2010-07-16-zeroing-weak-references-in-objective-c.html

    • 简单来说,系统有一个全局的 CFMutableDictionary 实例,来保存每个对象的 weak 指针列表,因为每个对象可能有多个 weak 指针,所以这个实例的值是 CFMutableSet 类型。剩下我们要做的,就是在引用计数变成 0 的时候,去这个全局的字典里面,找到所有的 weak 指针,将其值设置成 nil。如何做到这一点呢?Friday QA 上介绍了一种类似 KVO 实现的方式。当对象存在 weak 指针时,我们可以将这个实例指向一个新创建的子类,然后修改这个子类的 release 方法,在 release 方法中,去从全局的 CFMutableDictionary 字典中找到所有的 weak 对象,并且设置成 nil。我摘抄了 Friday QA 上的实现的核心代码,如下:

      1. Class subclass = objc_allocateClassPair(class, newNameC, 0);
      2. Method release = class_getInstanceMethod(class, @selector(release));
      3. Method dealloc = class_getInstanceMethod(class, @selector(dealloc));
      4. class_addMethod(subclass, @selector(release), (IMP)CustomSubclassRelease, method_getTypeEncoding(release));
      5. class_addMethod(subclass, @selector(dealloc), (IMP)CustomSubclassDealloc, method_getTypeEncoding(dealloc));
      6. objc_registerClassPair(subclass);
    • 当然,这并不代表苹果官方是这么实现的,因为苹果的这部分代码并没有开源。《Objective-C高级编程》一书中介绍了 GNUStep 项目中的开源代码,思想也是类似的。所以我认为虽然实现细节会有差异,但是大致的实现思路应该差别不大。

4、内存飙升问题解决

  • 问题:以下代码是否有问题?如果有,如何解决?

    1. // ARC
    2. long largeNumber = 5 * 1000 * 1000;
    3. for (long i = 0; i < largeNumber; ++i) {
    4. NSString *str = [NSString stringWithFormat:@"hello - %ld", i];
    5. str = [str uppercaseString];
    6. str = [str stringByAppendingString:@" - world"];
    7. }
  • 答案:如果 largeNumber 非常大,会创建太多自动释放的对象,有可能会把自动释放池 "撑满"。运行时会占用大量的系统内存空间。

    • 提示:经常一次用户交互,远远不止一个 for,在 for 的前后,会有很多的代码,但是这个 for 会占用大量的自动释放池空间。
  • 解决方法:引入自动释放池,有两种解决方案。

    • 1> 外面加自动释放池:能够保证 for 循环结束后,内部产生的自动释放对象,都会被销毁。需要等到 for 结束后,才会释放内存。不能解决运行时占用大量的系统内存的问题。

      1. long largeNumber = 5 * 1000 * 1000;
      2. NSLog(@"外面 start");
      3. CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
      4. // 自动释放池的标记,表示每一次运行循环监听到系统事件的时候,会自动创建自动释放池
      5. @autoreleasepool {
      6. for (long i = 0; i < largeNumber; ++i) {
      7. NSString *str = [NSString stringWithFormat:@"hello - %ld", i];
      8. str = [str uppercaseString];
      9. str = [str stringByAppendingString:@" - world"];
      10. }
      11. }
      12. NSLog(@"end - %f", CFAbsoluteTimeGetCurrent() - start);
    • 2> 内部加自动释放池:能够每一次 for 都释放产生的自动释放对象。能解决运行时占用大量的系统内存的问题。

      1. long largeNumber = 5 * 1000 * 1000;
      2. NSLog(@"内部 start");
      3. CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
      4. for (long i = 0; i < largeNumber; ++i) {
      5. // 自动释放池的标记,表示每一次运行循环监听到系统事件的时候,会自动创建自动释放池
      6. @autoreleasepool {
      7. NSString *str = [NSString stringWithFormat:@"hello - %ld", i];
      8. str = [str uppercaseString];
      9. str = [str stringByAppendingString:@" - world"];
      10. }
      11. }
      12. NSLog(@"end - %f", CFAbsoluteTimeGetCurrent() - start);
  • 提问:那种方式效率高。

    • 答案:速度差不多,大多数测试内部比外部快。

    • 提示:在日常工作中。有的时候,真的会出现代码内存飙升的情况。要知道解决办法,加自动释放池。

iOS - OC 内存管理的更多相关文章

  1. iOS学习17之OC内存管理

    1.内存管理的方式 1> iOS应用程序出现Crash(闪退),90%的原因是因为内存问题. 2> 内存问题 野指针异常:访问没有所有权的内存,如果想要安全的访问,必须确保空间还在 内存泄 ...

  2. 【0 - 1】OC内存管理

    一.内存管理概述 垃圾回收机制(GC):由系统管理内存,程序员不需要管理. OC中的垃圾回收:在OC2.0版加入垃圾回收. OC与iOS:OC有垃圾回收机制,但是iOS屏蔽了这个功能.原因:iOS运行 ...

  3. OC 内存管理机制总结

    OC 内存管理机制总结 一:OC内存管理机制目前分为两块,其一自动内存管理机制,其二手动内存管理机制: 1.首先我们从自动内存管理机制讲起: 1)什么是自动内存管理机制,自动内存管理机制就是程序中所创 ...

  4. iOS之内存管理(ARC)

    iOS的内存管理,相信大家都不陌生,之前是使用的MRC,由开发人员手动来管理内存,后来使用了ARC,来由系统管理内存.本文主要讲讲Autorelease,Core Foundation对象在内存管理方 ...

  5. OC内存管理总结,清晰明了!

    <span style="font-size:18px;">OC内存管理 一.基本原理 (一)为什么要进行内存管理. 由于移动设备的内存极其有限.所以每一个APP所占的 ...

  6. iOS的内存管理和引用计数规则、Block的用法以及三种形式(stack、malloc、global)

    学习内容 iOS的内存管理和引用计数规则 内存管理的思考方式 自己生成的对象自己持有 非自己生成的对象自己也能持有 自己持有的对象不需要时释放 非自己持有的对象不能释放 ARC有效时,id类型和对象类 ...

  7. 理解 iOS 的内存管理

    远古时代的故事 那些经历过手工管理内存(MRC)时代的人们,一定对 iOS 开发中的内存管理记忆犹新.那个时候大约是 2010 年,国内 iOS 开发刚刚兴起,tinyfool 大叔的大名已经如雷贯耳 ...

  8. iOS ARC内存管理

    iOS的内存管理机制,只要是iOS开发者,不管多长的时间经验,都能说出来一点,但是要深入的理解.还是不简单的.随着ARC(自动管理内存)的流行.iOS开发者告别了手动管理内存的复杂工作.但是自动管理内 ...

  9. OC内存管理基础

    OC 内存管理基础 一. retain和release基本使用 使用注意: 1.你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作) 2.你不想再使用(占用)某个对象,就 ...

随机推荐

  1. Linux之Ganglia源码安装

    一.Ganglia简介: Ganglia是UC Berkeley发起的一个开源集群监视项目,设计用于测量数以千计的节点.Ganglia的核心包含gmond.gmetad以及一个Web前端.主要是用来监 ...

  2. MySQL连接字符串总结

    一.MySQL Connector/ODBC 2.50 (MyODBC 2.50)连接方式 1.本地数据库连接 Driver={MySQL};Server=localhost;Option=16834 ...

  3. 浅谈SQL中的单引号

    单引号:对很对计算机语言包括(SQL)是做字符串引用的:这个是大家通常知道的作用:但是对SQL语言来说:还有另外一个作用是作引号的转义 总结下:对oracle(sql)的作用. 做字符串引用:例如'a ...

  4. 线程和进程详解(以java为例具体说明)

    详细参见http://ifeve.com/java-concurrency-thread-directory/ 一.线程概述 线程是程序运行的基本执行单元.当操作系统(不包括单线程的操作系统,如微软早 ...

  5. 字符编码的过滤器Filter(即输入的汉字,能在页面上正常显示,不会出现乱码)

    自定义抽象的 HttpFilter类, 实现自 Filter 接口 package com.lanqiao.javaweb; import java.io.IOException; import ja ...

  6. poj1703 Find them, Catch them

    并查集. 这题错了不少次才过的. 分析见代码. http://poj.org/problem?id=1703 #include <cstdio> #include <cstring& ...

  7. UML类图几种关系的总结(转)

    原文:http://gjhappyyy.iteye.com/blog/1422515 在UML类图中,常见的有以下几种关系: 泛化(Generalization),  实现(Realization), ...

  8. 二叉搜索树的后序遍历路径(《剑指offer》面试题24)

    题目:输入一个整数数组,判断该数组是不是二叉搜索树的后序遍历序列的结果,如果是,则返回true,如果不是则返回false.假设输入的数组的任意两个数字都互不相同. 分析:在后序遍历得到的序列中,最后一 ...

  9. isp和3a的联系与区别是什么?

    Willis Zen上善若水 2 人赞同 你说的这个问题,不是很多人能够回答的,我也只能把我知道的告诉你.isp 是image signal processing,用于图像处理,比如gamma调整,d ...

  10. Entity Framework 第一篇

    这段时间研究了orm框架EF 写一写研究的历程和心得 先贴上核心代码 public interface ITransaction { bool IsTransaction { get;} void B ...