iOS MRC 内存管理的基本原则

iOS MRC内存管理精要
本文深入解析iOS MRC(手动引用计数)内存管理原理,探讨引用计数规则、autorelease延迟释放机制及特殊场景处理,提升iOS开发者的代码质量和应用性能。

iOS MRC 内存管理的基本原则

搞 iOS 将近一年了,还没怎么写过 iOS 相关的文章,接下来我要开始「干正事」了!

先从内存管理开始。对于一个软件来说,合理的使用内存可以让应用的性能和体验更加优秀。之前搞 Android 时的内存管理就比较简单,基于 JVM 的强大垃圾回收能力,开发者只需要考虑一些常见的内存泄漏问题即可。

转到 iOS 后,就需要了解清楚内存管理的一些知识,提高写代码的姿势水平。

在通读官方文档 Advanced Memory Management Programming Guide[1] 之后,写一写我的理解和总结。

引用计数与 MRC

iOS 内存管理的核心是管理「引用计数 (Reference Counting)」。MRC 的全称是 Mannul Reference Counting。所以,iOS 内存管理,就是手动管理对象的引用计数。

每一个对象都会被若干个对象持有,其他象持有和放弃持有一个对象,实际上是让该对象的引用计数 +1 或者 -1。当一个对象的引用计数减为 0 时,就会被立刻释放。

对一个对象发送 retain 消息,可以让对象的引用计数 +1,对一个对象发送 release 消息,可以让对象的引用计数 -1。当对象的引用计数为 0 时就会被立刻释放。

规则是相当的简单!

但是出来混迟早要还的,规则简单,就意味着在使用时需要各种约定来保证不出问题。

比如最容易想到的一个问题:引用计数的归属问题。既然我可以通过 release 达到引用计数 -1 的目的,是不是就可以随意 release 呢?肯定不是,这样的话可能你正在使用的一个对象,被其他对象调用了 release 然后被释放,这肯定是不行的。

基于引用计数的简单规则下的引用计数归属问题

为了保证对象能正确的被 retain 和 release,需要制定一系列的规则来约束开发者,从而达到正确管理引用计数的目的。

调用方法名以 “alloc”, “new”, “copy”, or “mutableCopy” 开头的方法创建的对象,方法调用者负责后续调用 release 将引用计数 -1

这规则,真 Apple,简单粗暴。

{
    Person *aPerson = [[Person alloc] init];
    // ...
    NSString *name = aPerson.fullName;
    // ...
    [aPerson release];
}

如上面的例子所示,aPerson 这个对象通过 alloc 方法获得,aPerson 的引用计数为 1,并且负责后续 release。

name 通过函数 fullName 获得,不是以上面提到的关键字开头的,所以不需要负责后续 release。

通过 retain 使对象的引用计数 +1,并负责后续发送 release 消息
{
    Person *aPerson = [Person person];
    [aPerson retain];
    // ...
    [aPerson release];
}
归属问题的总原则

总结为一句话就是「谁让引用计数增加 n,谁就负责减少 n」。

一些需要特殊处理的情况

使用 autorelease 发送延迟的 release 操作

上面提到的 release 操作会将对象的引用计数立刻 -1,这样在很多时候,会引起对象立即被释放。

- (NSString *)fullName {
    NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",
                                          self.firstName, self.lastName] autorelease];
    return string;
}

上面这个例子中,调用者通过调用 fullName 来获得一个字符串对象,但是根据上面提到的原则,调用者不负责后续调用 release,由 fullName 函数内进行 release,但是 release 之后对象 string 会被立刻释放,调用者就拿不到这个对象了。

要解决这个问题,就要使用 autorelease,顾名思义,自动释放,实际上是和自动释放池配合使用。

在自动释放池内发送了 autorelease 消息的对象,都会在自动释放池结束的时候被发送一次 release 消息,从而达到延迟释放的目的。

@autoreleasepool {
    // Code that creates autoreleased objects.
    NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",
                                          self.firstName, self.lastName] autorelease];
}

关于自动释放池,AppKit 和 UIKit 框架为每一个 Runloop 自动包裹了一个自动释放池。因此,通常不必自己创建一个自动释放池。但是,在下面三种情况下,可能会使用自己的自动释放池:

  • 如果您正在编写的程序不基于 UI 框架,例如命令行工具。

  • 如果您编写一个循环来创建许多临时对象。您可以在循环中使用 autorelease 池块在下一次迭代之前处理这些对象。在循环中使用自动释放池块有助于减少应用程序的最大内存占用。

  • 如果生成一个子线程,一旦线程开始执行,您必须创建自己的自动释放池块;否则,您的应用程序将泄漏对象。

你不持有通过引用返回的对象

在 Cocoa 的一些方法中,指定通过引用返回一个对象(参数是 ClassName *_ 或者 id _)。常见的模式是使用 NSError 对象,该对象包含发生错误时有关错误的信息。

当你执行这些方法时,你并没有拥有通过引用返回的对象,所以也不需要调用 release 释放它。用下面这个例子说明一下:

NSString *fileName = <#Get a file name#>;
NSError *error;
NSString *string = [[NSString alloc] initWithContentsOfFile:fileName
                        encoding:NSUTF8StringEncoding error:&error];
if (string == nil) {
    // Deal with error...
}
// ...
[string release];

通过二级指针来获取一个对象,在 Cocoa 中很多地方有这种用法,最经典的就是上面例子中的 initWithContentsOfFile,通过二级指针很方便的取回一个对象。但是这种获得对象的方式并不符合上面提到的引用计数相关的原则,所以这里其实是增加了一种规则。

总结

MRC 的规则相当简单:

对一个对象发送 retain 消息,可以让对象的引用计数 +1,对一个对象发送 release 消息,可以让对象的引用计数 -1。当对象的引用计数为 0 时就会被立刻释放。

简单的规则之上,需要约定很多的规则,来满足和完善复杂的开发场景。所幸现在的 iOS 开发早就切到 ARC(自动引用计数)了。在 ARC 的支持下,我们可以像写 Java 代码那样,不需要去了解和管理引用计数,因为系统已经帮我们做了。

但是,ARC 只是在编译器帮助开发者插入了合适的引用计数管理代码而已,了解清楚引用计数的细节,有助于我们更深层次的理解内存管理,提高个人技术深度与能力。

我这边有翻译了几篇官方文档,有需要的可以看一下,官方文档的质量还是挺高的,对一些事情说的比较直接透彻,只是翻译过来有些句子读着怪怪的,我加了一部分解释。有需求的可以点击阅读原文查看。

参考资料

[1]

Advanced Memory Management Programming Guide: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html

<think>好的,用户让我介绍一下iOS内存管理及其底层原理。我需要先回忆一下相关知识,确保信息准确。首先,iOS内存管理主要依赖自动引用计数(ARC),这点是基础。然后,得解释ARC是怎么工作的,比如引用计数的增减,对象的持有和释放。可能还需要提到MRC,也就是手动管理时代的情况,作为对比,突出ARC的优势。 接下来,用户可能想知道底层原理,所以需要深入一点。比如引用计数是如何存储的,是使用SideTable还是其他结构。这里要确认一下,SideTable在iOS中的具体实现,是否有多个SideTable来减少锁竞争。还有weak指针的实现,比如如何自动置nil,避免野指针。 另外,内存管理中的常见问题,比如循环引用,需要举例说明,比如block中的self使用,这时候应该提到__weak修饰符。还有autoreleasepool的作用,比如在循环中大量创建临时对象时,如何利用autoreleasepool及时释放内存。 可能还需要提到内存管理的其他方面,比如内存警告的处理,ViewController的didReceiveMemoryWarning方法,开发者需要在这里释放不必要的资源。还有内存的分区,比如栈、堆、全局区等,但用户可能更关注ARC和底层机制,这部分可以简要带过。 需要检查是否有最新的信息,比如Swift中的内存管理是否有所不同,但题目是iOS,可能包括OC和Swift,不过ARC在两者中都是基础。不过Swift使用了更简洁的语法,比如strong、weak、unowned,可能需要简单提及。 还要注意结构清晰,分点回答,比如分ARC原理、底层机制、常见问题、优化策略等部分。同时要使用用户要求的格式,数学公式用$...$,独立公式用$$...$$。例如,引用计数的变化可以用数学表达式表示,比如引用计数加1减1的操作。 可能存在的疑问点:SideTable的具体结构,比如引用计数和weak表是否真的存在散列表中,需要确认资料来源是否准确。此外,Tagged Pointer的概念,是否属于内存管理的一部分,比如小对象的存储优化,避免内存分配,这也是iOS内存管理的一个优化点,应该包含进去。 最后,确保语言通俗易懂,避免过于晦涩的技术术语,但又要准确。比如解释weak指针时,可以说明它们如何不增加引用计数,并在对象释放时自动置nil,防止悬挂指针。 总结一下,回答的结构大致分为几个部分:ARC的基本原理,底层实现细节(引用计数存储、SideTable、weak机制),常见问题及解决方法(循环引用、内存泄漏),以及优化技巧(如使用autoreleasepool、处理内存警告)。同时,要确保符合格式要求,正确使用LaTeX,特别是引用计数的数学表达式可能需要用$...$包裹。</think>我将从iOS内存管理核心机制、底层原理、常见问题及优化策略三个层次进行解析: $$内存管理模型 = 自动引用计数(ARC) + 底层对象生命周期控制$$ 一、核心机制:自动引用计数(ARC) 1. 基本工作原理 - 每个对象内置引用计数器:$RC_{obj}$ - 当$RC_{obj} > 0$时对象存活,$RC_{obj} = 0$时立即释放 - 操作示例: $$[obj retain] \Rightarrow RC_{obj} += 1$$ $$[obj release] \Rightarrow RC_{obj} -= 1$$ 2. 所有权修饰符(Swift对应概念) | 修饰符 | 作用特性 | 使用场景 | |------------|----------------------------|------------------------| | __strong | 默认修饰符,增加RC | 常规对象持有 | | __weak | 不增加RC,自动nil化 | 打破循环引用 | | __unsafe_unretained | 不增加RC,不自动nil化 | 性能敏感场景(需确保生命周期)| | __autoreleasing | 延迟释放 | 方法参数传递 | 二、底层实现原理 1. 引用计数存储优化 - Tagged Pointer:小对象直接编码在指针中,无真实内存分配 $$Pointer_{tagged} = 标志位(1bit) + 数据类型(3bits) + 有效载荷(60bits)$$ - Non-pointer isa:64位系统中利用isa指针冗余位存储RC - SideTable散列表:当RC溢出时转存至分离结构 2. SideTable结构(线程安全实现) ```c++ struct SideTable { spinlock_t lock; // 自旋锁 RefcountMap refcnts; // 引用计数哈希表 weak_table_t weak_table; // weak指针登记表 }; ``` 系统维护全局SideTables(多个SideTable实例),通过对象指针哈希选择对应表,降低锁竞争 3. Weak机制实现 - 注册阶段:将weak指针地址记录到weak_table - 清除阶段:对象dealloc时遍历weak_table ① 将weak指针指向nil ② 从weak_table移除记录 ③ 具体操作: $$ \forall ptr \in weak\_table[obj], \quad *ptr = nil $$ 三、典型问题与优化 1. 循环引用场景 - 对象相互强引用:A.objB ↔ B.objA - 解决方案: ```objective-c __weak typeof(self) weakSelf = self; block = ^{ __strong typeof(weakSelf) strongSelf = weakSelf; [strongSelf doSomething]; }; ``` 2. 内存峰值优化 - AutoreleasePool合理使用: ```objective-c for (int i=0; i<100000; i++) { @autoreleasepool { NSString *temp = [NSString stringWithFormat:@"%d",i]; // 处理临时对象 } // 池结束时立即释放 } ``` 3. 内存警告处理 ```objective-c - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; [self clearCache]; // 主动释放可重建资源 [self reloadData]; // 必要时重建数据 } ``` 四、底层方法调用链 1. 对象释放过程: retainCount → release → dealloc → _objc_rootDealloc → object_dispose 2. 内存分配过程: alloc → _objc_rootAlloc → calloc → vm_allocate 性能监控建议: - 使用Xcode Memory Graph定位循环引用 - Instruments的Allocations工具分析内存分配模式 - VM Tracker检测内存压缩质量 通过理解这些机制,开发者可以更精准地控制内存使用,在保证应用流畅度的同时降低被系统终止的风险。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值