face it-iOS-内存管理

内存

内存分为:代码段、数据段、堆区、栈区、内核区


m
  • 代码段:编译之后的代码
  • 数据段
    字符串常量:如NSString *str = @"123";
    已初始化数据:已初始化的全局变量静态变量
    未初始化数据:未初始化的全局变量静态变量
  • 栈:函数调用开销,比如局部变量。分配的内存空间地址越来越小
  • 堆:通过allocmalloccalloc等动态分配的空间,分配的内存空间地址越来越大

内存泄漏

指程序中间动态分配了内存,但在程序结束时 或使用完这块内存后,没有释放这部分内存,从而造成那一部分内存不可用的情况。
一般的,内存泄漏是指 堆(heap)内存的泄漏。
真正有危害的是内存泄漏的积累,这会最终耗尽系统所有的内存。

iOS内存管理

内存管理的目的:避免“过早释放”和“内存泄漏”。

基本思想就是 引用计数。通过它来控制内存对象的生命周期。

1、引用计数(Reference Count)

一个简单有效地管理对象生命周期的方式。在OC内存管理中,每个对象都有自己的计数器,表示对象被引用的次数。

2、引用计数的工作原理

当创建一个新对象时,它的引用计数为 1;
当有一个新的指针指向这个对象时,我们将其引用计数加 1;
当某个指针不再指向这个对象是,我们将其引用计数减 1;
当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这时就可以将对象销毁,回收内存。

当创建(alloc)一个新对象A时,它的引用计数从0变为 1;
当有一个指针指向这个对象A,也就是某对象想通过引用保留(retain)该对象A时,引用计数+1;
当某个指针/对象不再指向这个对象A,也就是释放(release)该引用后,我们将其引用计数-1;
  • 引用计数的存储
    在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中

内存管理原则
创建的对象,当不再需要时,释放掉。
需要使用的对象,保留。
如果没必要释放,不要释放没有拥有的对象。

3、ARC与MRC

iOS内存管理主要有两种机制:MRC、ARC。

  • MRC(手工引用计数)
    对象的生成、销毁、引用计数的变化都是由开发人员来完成

  • ARC(自动引用计数)
    2011年在MacOS X 10.7iOS 5中引入的新技术,用于代替之前的MRC。ARC下几乎把所有内存管理事宜,都交给编译器。(ARC下会自动生成retain、release、autorelease)

ARC 的原理是依赖编译器的静态分析能力,通过在编译时找出合理的插入引用计数管理代码,从而彻底解放程序员。
ARC机制由 LLVM编译器 + Runtime系统,相互协作

ARC的作用:
降低内存泄露等风险 ; 开发者只负责对象的生成,不需要关心其销毁。专注于业务逻辑。

4、ARC的局限

ARC 能够解决 iOS 开发中 90% 的内存管理问题,但另外 10% 内存管理,是需要开发者自己处理的。就是:

  • 过度使用block之后,解决循环引用问题。
  • 与底层 Core Foundation对象交互的那部分。底层的Core Foundation 对象由于不在 ARC 的管理下,所以需要自己维护其引用计数。

循环引用问题

  • 例:
    对象 A 和对象 B,相互引用了对方作为自己的成员变量,只有当自己销毁时,才会将成员变量的引用计数减 1。
    因为 A 的销毁依赖于 B 销毁,而 B 的销毁又依赖于 A 的销毁,这样就造成了“循环引用”。这两个对象即使在外界已经没有任何指针能够访问到它们了,它们也无法被释放。
    另外,多个对象依次持有对方,形式一个环状,也可以造成循环引用。

解决循环引用问题,主要有2个办法

办法1:主动置nil

明确知道会存在循环引用,在合理的位置和时机,主动断开环中的一个引用,使得对象得以回收。常见于各种与 block 相关的代码逻辑中。如:一般在最后把block置nil

办法2:使用弱引用weak

弱引用虽然持有对象,但并不增加引用计数,也就避免了循环引用。在 iOS 开发中,通常在 delegate 模式中使用。

原理:系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。这样,当一个对象的引用计数为 0 时,系统就通过这张表,找到所有的弱引用指针,继而把它们都置成 nil。
可见,弱引用的使用是有额外的开销的。虽然开销很小,但如果一个地方肯定它不需要弱引用,就不应该盲目使用弱引用。

Core Foundation 对象的内存管理

创建对象

// 创建一个 CFStringRef 对象
CFStringRef str= CFStringCreateWithCString(kCFAllocatorDefault, “hello", kCFStringEncodingUTF8);

// 创建一个 CTFontRef 对象
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);

对于这些对象的引用计数的修改,使用 CFRetainCFRelease 方法,手工管理引用计数。

此外,还有另外一个问题。在 ARC 下,有时需要将一个Core Foundation 对象转换成Objective-C对象,这时需要告诉编译器,转换过程中的引用计数需要做如何的调整。这就引入了bridge相关的关键字。

  • __bridge: 只做类型转换,不修改相关对象的引用计数,
    原来的 Core Foundation 对象在不用时,需调用 CFRelease方法。
  • __bridge_retained:类型转换后,将相关对象的引用计数加 1,
    原来的Core Foundation对象在不用时,需调用CFRelease方法。
  • __bridge_transfer:类型转换后,将该对象的引用计数交给 ARC 管理,Core Foundation 对象在不用时,不再需要调用 CFRelease方法。

根据具体的业务逻辑,合理使用上面的 3 种转换关键字,就可以解决 Core Foundation对象与 Objective-C对象相对转换的问题了。

定时器的内存管理

定时器使用完毕时需要将其停止,并置nil

自动释放池autorelease

执行方法autorelease不立即释放,而是注册到(自动释放池)中,等到pool结束时释放池,再自动调用release进行释放工作。

自动释放池的主要底层数据结构是:
__AtAutoreleasePoolAutoreleasePoolPage

调用了autorelease的对象,最终都是通过AutoreleasePoolPage对象来管理

  • AutoreleasePoolPage的结构
    每个AutoreleasePoolPage对象占用4096字节内存,除了存放它的成员变量,剩下的空间用来存放autorelease对象的地址。所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起
-

调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址;
调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
id *next指向了下一个能存放autorelease对象地址的区域。

  • 关键字autoReleasePool
for (int i = 0; i < 100000; i++) {

    NSString *string = @"Abc";
    string = [string lowercaseString];
    string = [string stringByAppendingString:@"xyz"];
    NSLog(@"%@", string);
}

该for循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏。解决方法是在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。

for (int i = 0; i < 100000; i++) {
    @autoreleasepool {
        NSString *string = @"Abc";
        ...
        NSLog(@"%@", string);
    }
}

ARC 下,当使用alloc/new/copy/mutableCopy开始的方法 进行初始化时,系统会自动 在合适位置release,不需要pool进行管理。

主线程默认为我们开启 Runloop,Runloop 会自动创建Autoreleasepool,并进行Push、Pop 等操作来进行内存管理。

使用内存管理工具

可以用Xcode工具仪器的帮助下,分析内存的使用情况。它包括的工具有活动监视器,分配,泄漏,僵尸等。

菜单栏选择:Product -> Profile


m

iOS 模拟器会运行起来,模拟器里切换一些界面。稍等几秒钟,就可以看到 Instruments 检测到了我们的这次循环引用。


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