iOS之内存管理(原理篇)

之前就总结过内存管理的内容,但并不系统、全面。所以,一直想找时间好好把这一块内容规整一下,因为说起内存管理,这是一个很庞大而又与开发密不可分的内容,虽然ARC下我们几乎不需要手动管理内存,但我们还是要了解一些内存管理的原理和内存优化的方案。

一.iOS内存管理基本原理

iOS和其它系统一样,内存分页,每页4K。多个页构成一个region统一管理,负责管理的对象是VM object,其中包含了pager、size、resident pages等诸多属性。
不管是Objective-C的[NSObject alloc],还是C代码的对内存分配,最终重任都会落到malloc库上,释放也是如此,最终都将使用malloc库中的free()。
当内存吃紧时,对于可以重新载入的只读数据来说,直接清理掉,而对于可写的数据,只能通过App自己去管理维护。内存紧张时,iOS会向App发起memory warning,不配合释放足够内存者,kill!

二. ARC(引用计数)

1. 何为引用计数机制?

ARC 是 WWDC2011 和 iOS5 引入的变化。ARC 是 LLVM 3.0 编译器的特性,用来自动管理内存。
与 Java 中 GC 不同,ARC 是编译器特性,而不是基于运行时的,所以 ARC 其实是在编译阶段自动帮开发者插入了管理内存的代码,而不是实时监控与回收内存。

以经典的开灯例子举例,在办公室内至少有一人保持开灯状态,无人时保持关灯状态

最早进入办公室的人   ===> 开灯      ==== > 生成对象 
之后进入办公室的人   ===> 需要照明   ==== > 持有对象 
下班离开办公室的人   ===> 不需要照明  ==== > 释放对象
最后下班离开办公室的人===> 关灯       ==== >  废弃对象
中间无论多少人,只要遵循进来就需要照明,走就不要照明。这样办公室的照明就可以得到很好的管理;同理使用引用计数功能,对象也能够得到很好的管理。

原则:

1)  当对象被创建(通过alloc、new或copy等方法)时,其引用计数初始值为1。
2 )  持有对象,当对象被强引用时,引发retain方法,引用计数加1。
3)  对象被释放时,引发release操作,引用计数减1。
4)  当引用计数为0时,引发dealloc操作,对象被销毁废弃。

注意:不可对已经被销毁的对象发送消息

2. ARC的修饰符

ARC的修饰符提供成员变量访问方法、权限、环境、内存管理类型的声明。
属性的参数分为三类,基本数据类型默认为(atomic,readwrite,assign),对象类型默认为(atomic,readwrite,strong),其中第三个参数就是该属性的内存管理方式修饰,修饰词可以是以下之一:

  1. strong:强引用(引用计数+1),持有所指向对象的所有权,无修饰符情况下的默认值。如需强制释放,可置nil。
  2. weak:弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针。
    使用set方法赋值时,实质上不保留新值,也不释放旧值,只设置新值。
  3. retain:release旧值,再retain新值(引用计数+1)
    使用set方法赋值时,实质上是会先保留新值,再释放旧值,再设置新值,避免新旧值一样时导致对象被释放的的问题。
  4. copy:release旧值,再copy新值(拷贝内容)
    一般用来修饰String、Dict、Array等需要保护其封装性的对象,尤其是在其内容可变的情况下,因此会拷贝(深拷贝)一份内容給属性使用,避免可能造成的对源内容进行改动。
    使用set方法赋值时,实质上是会先拷贝新值,再释放旧值,再设置新值。
    实际上,遵守NSCopying的对象都可以使用copy,当然,如果你确定是要共用同一份可变内容,你也可以使用strong或retain。
  5. assign:直接赋值,一般用来修饰基本数据类型
3. block的内存管理

iOS中使用block必须自己管理内存,错误的内存管理将导致循环引用等内存泄漏问题,这里主要说明在ARC下block声明和使用的时候需要注意的两点:
1) 如果你使用@property去声明一个block的时候,一般使用copy来进行修饰(当然也可以不写,编译器自动进行copy操作),尽量不要使用retain。

 @property (nonatomic, copy) void(^block)(NSData * data);

2) block会对内部使用的对象进行强引用,因此在使用的时候应该确定不会引起循环引用,当然保险的做法就是添加弱引用标记。

 __weak typeof(self) weakSelf = self;
4. autorelease和autorelaesepool

官方文档解释:

每个线程(包括主线程),都维护了一个管理 NSAutoreleasePool 的栈。当创先新的 Pool 时,他们会被添加到栈顶。当 Pool 被销毁时,他们会被从栈中移除。
autorelease 的对象会被添加到当前线程的栈顶的 Pool 中。当 Pool 被销毁,其中的对象也会被释放。
当线程结束时,所有的 Pool 被销毁释放。

autorelease 顾名思义及时自动释放的意思。它有点类似 C 语言中自动变量的特性。程序执行时,若自动变量超出其作用域,该自动变量将被自动废弃。

1. autorelease具体的使用方法:

  • 生成并持有 NSAutoreleasePool 对象
  • 调用已分配对象的 autorelease 实例方法
  • 废弃 NSAutoreleasePool 对象
使用场景:需要延迟释放某些对象的情况时,可以把他们先放到对应的Autorelease Pool中,等Autorelease Pool生命周期结束时再一起释放。

利用@autoreleasepool优化循环的内存占用,可能是我们会用到的一点。如下面的循环,次数非常多,而且循环体里面的对象都是临时创建使用的,就可以用@autoreleasepool包起来,让每次循环结束时,可以及时的释放临时对象的内存。

for (int i = 0; i < 10000; i++) {
     @autoreleasepool {
         // 创建对象 TODO:
          NSObject *obj = [[NSObject alloc]init];
    };
}

2. Autorelease对象什么时候释放?
在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop
ARC 中我们使用@autoreleasepool{}来使用一个AutoreleasePool的是时候,编译器就将其改写成下面的样子:

void *context = objc_autoreleasePoolPush();
// {}中的代码,所有接收到 autorelease 消息的对象会被添加到这个 autoreleasepool 中
objc_autoreleasePoolPop(context);

而这两个函数都是对AutoreleasePoolPage的简单封装,所以自动释放机制的核心就在于这个类。AutoreleasePoolPage是一个C++实现的类。

*  AutoreleasePool并没有单独的结构,而是由若干个``AutoreleasePoolPage``以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
*  AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
*  ``AutoreleasePoolPage``每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
*  上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
*  一个``AutoreleasePoolPage``的空间被占满时,会新建一个``AutoreleasePoolPage``对象,连接链表,后来的``autorelease``对象在新的page加入

所以,若当前线程中只有一个AutoreleasePoolPage对象,并记录了很多autorelease对象地址时内存如下图:
![Upload 51530583gw1elj5gvphtqj20dy0cx756.jpg failed. Please try again.]
图中的情况,这一页再加入一个autorelease对象就要满了(也就是next指针马上指向栈顶),这时就要执行上面说的操作,建立下一页page对象,与这一页链表连接完成后,新page的next指针被初始化在栈底(begin的位置),然后继续向栈顶添加新对象。
所以,向一个对象发送-autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置

释放
每当进行一次objc_autoreleasePoolPush调用时,runtime向当前的AutoreleasePoolPage中add进一个哨兵对象,值为0(也就是个nil),那么这一个page就变成了下面的样子:


objc_autoreleasePoolPush的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(哨兵对象)作为入参,于是:

  1. 根据传入的哨兵对象地址找到哨兵对象所处的page
  2. 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release消息,并向回移动next指针到正确位置
  3. 补充2:从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page

刚才的objc_autoreleasePoolPop执行后,最终变成了下面的样子:


51530583gw1elj6u2i3fyj20dz0bqdgi.jpg

这篇,主要讲了内存管理的原理和概念性的东西,下一篇会针对内存泄漏及内存优化解决方案做详细的介绍

亲,喜欢的话点个赞呗!

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

推荐阅读更多精彩内容

  • 29.理解引用计数 Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数...
    Code_Ninja阅读 1,470评论 1 3
  • 内存管理 简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与a...
    丶逐渐阅读 1,948评论 1 16
  • 前言 现在iOS开发已经是arc甚至是swift的时代,但是内存管理仍是一个重点关注的问题,如果只知盲目开发而不知...
    明仔Su阅读 26,527评论 16 175
  • iOS内存管理 概述 什么是内存管理 应用程序内存管理是在程序运行时分配内存(比如创建一个对象,会增加内存占用)与...
    蚊香酱阅读 5,693评论 8 119
  • 内存管理 ARC处理原理 ARC是Objective-C编译器的特性,而不是运行时特性或者垃圾回收机制,ARC所做...
    b485c88ab697阅读 11,180评论 3 47