核心说明:聚焦面试高频提问,围绕ARC与MRC的底层原理、核心区别、实操场景、常见问题展开,全程直击考点,无冗余表述,兼顾理论深度与面试应答实用性,可直接用于面试背诵和应答。
一、核心基础:引用计数(ARC与MRC的共同底层核心)
面试必记:ARC与MRC的底层核心都是「引用计数(Reference Counting)」,本质是通过计数器记录对象被持有个数,实现内存的分配与释放,遵循"谁持有,谁释放"的内存管理黄金法则------谁使对象引用计数+1,谁就负责使引用计数-1,当引用计数为0时,对象会被系统销毁,释放占用内存。
1.1 引用计数底层存储(面试高频延伸)
-
底层载体:引用计数通过Runtime的「SideTable结构」管理,SideTable是全局哈希表,每个对象对应一个SideTable条目;
-
核心结构:SideTable包含三个核心部分------RefcountMap(存储对象引用计数)、weak_table(存储对象的弱引用列表)、spinlock_t(自旋锁,保证多线程下引用计数操作的线程安全);
-
计数操作:引用计数的+1(retain)、-1(release)操作,本质是对RefcountMap中对应对象的计数进行原子操作,避免多线程竞争问题。
1.2 引用计数核心变化规则(必背,面试直接考)
-
对象创建(alloc/new/copy/mutableCopy):引用计数初始值为1;
-
持有对象(retain/strong指针赋值):引用计数+1;
-
释放对象(release/strong指针置nil):引用计数-1;
-
对象销毁:当引用计数降至0时,系统自动调用对象的dealloc方法,销毁对象并释放其占用的堆内存。
二、MRC(手动引用计数)------ 底层原理与面试考点
2.1 底层原理(核心必记)
MRC(Manual Reference Counting),即手动引用计数,底层依赖开发者手动调用retain、release、autorelease等方法,控制引用计数的变化,进而管理内存,无编译器自动干预,完全依赖开发者遵循内存管理法则。
-
核心核心:开发者全程掌控引用计数的增减,需手动匹配"retain"与"release"操作,确保引用计数平衡;
-
autorelease底层:MRC中可通过autorelease将对象加入自动释放池(autoreleasepool),对象不会立即释放,而是在当前RunLoop循环结束时,自动执行release操作,使引用计数-1,若计数为0则销毁对象,适用于临时对象的延迟释放;
-
底层调用:retain、release、autorelease本质是调用Runtime的objc_retain、objc_release、objc_autorelease函数,操作SideTable中的引用计数。
2.2 面试高频考点(必背)
-
核心操作(必写):
-
创建对象:NSString *str = [[NSString alloc] initWithString:@"test"];(计数=1);
-
持有对象:[str retain];(计数=2);
-
释放对象:[str release];(计数=1);
-
最终释放:[str release];(计数=0,对象销毁);
-
临时对象:NSString *tempStr = [NSString stringWithFormat:@"temp"];(自动加入autoreleasepool,RunLoop结束后释放)。
-
-
常见问题(面试必问):
-
野指针:对象被release后未置nil,后续仍访问该对象(此时对象已销毁,内存可能被复用),导致崩溃;
-
内存泄漏:忘记调用release,导致对象引用计数无法降至0,无法被销毁,长期占用内存;
-
过度释放:对同一对象多次调用release,导致引用计数为负,触发内存异常崩溃。
-
-
面试延伸:MRC中@property的修饰符(assign/retain/copy),需手动实现setter方法,匹配retain和release(例:retain修饰的setter需先release旧值,再retain新值)。
2.3 适用场景(面试延伸)
iOS 5之前的项目,目前已完全淘汰;仅在维护老旧项目、移植早期Unix程序或底层Runtime开发时可能接触,面试重点考察对其原理和问题的理解,而非实际开发使用。
三、ARC(自动引用计数)------ 底层原理与面试考点(重中之重)
3.1 底层原理(核心必记,区别于MRC的关键)
ARC(Automatic Reference Counting),即自动引用计数,是iOS 5引入的内存管理机制,底层核心仍是引用计数,核心区别是「编译器自动插入retain、release、autorelease等代码」,开发者无需手动调用,由编译器和Runtime协同管理内存,本质是"自动帮开发者完成MRC的手动操作",但底层逻辑与MRC完全一致,遵循相同的内存管理法则。
-
核心核心:编译器通过静态分析,识别对象的生命周期,在合适的位置(如变量作用域结束、指针置nil时)自动插入引用计数操作代码,无需开发者干预;
-
Runtime协同:ARC依赖Runtime的weak指针机制、SideTable结构,以及objc_autoreleasePoolPush/Pop等函数,处理弱引用自动置nil、自动释放池管理等逻辑;
-
底层限制:ARC禁止开发者手动调用retain、release、autorelease、dealloc(可重写dealloc,但不能调用[super dealloc]),否则编译报错;
-
autoreleasepool优化:ARC中自动释放池仍存在,编译器会自动在合适位置插入autoreleasepool相关代码,大循环中手动添加@autoreleasepool可降低内存峰值,避免内存溢出。
3.2 面试高频考点(必背,占比最高)
-
ARC核心修饰符(必记,区别于MRC):
-
strong(强引用,默认):持有对象,使引用计数+1,只要有strong指针持有对象,对象就不会被销毁;
-
weak(弱引用):不持有对象,不改变引用计数,当对象被销毁时,weak指针会被Runtime自动置为nil,避免野指针(核心作用:解决循环引用);
-
assign(弱引用,适用于基本数据类型):不持有对象,对象销毁后指针不会自动置nil,易产生野指针(仅用于int、float等基本类型,不用于对象);
-
copy(强引用,深拷贝):适用于不可变对象(如NSString),避免因原对象修改导致自身被篡改,本质是创建新对象,引用计数初始为1。
-
-
ARC的循环引用(面试难点,必考):
-
核心原因:两个或多个对象互相持有strong引用,导致引用计数无法降至0,对象无法被销毁,造成内存泄漏(ARC的核心痛点,MRC中也存在,但ARC更易被忽略);
-
常见场景(必背):
-
block与控制器:控制器持有block,block内部持有控制器(self);
-
代理模式:控制器强引用代理对象,代理对象强引用控制器;
-
父子对象:父对象strong持有子对象,子对象strong持有父对象。
-
-
解决方案(必背,结合实操):
-
block场景:使用__weak修饰self(weakSelf),block内部通过weakSelf访问控制器,打破循环;
-
代理模式:代理属性用weak修饰,控制器弱引用代理对象;
-
父子对象:一方用weak修饰,打破强引用循环;
-
补充:__unsafe_unretained(慎用),不持有对象,对象销毁后指针不置nil,易产生野指针,仅兼容低版本iOS。
-
-
-
ARC底层优化(面试延伸):
-
编译器优化:当编译器识别到对象仅在当前作用域使用时,会省略retain/release操作,提升性能;
-
weak指针实现:weak指针的底层依赖SideTable的weak_table,对象销毁时,Runtime遍历weak_table中该对象的所有弱引用,将其置为nil;
-
Core Foundation对象兼容:ARC中使用Core Foundation框架创建的对象(如CFStringRef),需手动管理内存(CFRetain/CFRelease),否则会导致内存泄漏。
-
四、ARC 与 MRC 核心区别(面试必问,对比记忆)
| 对比维度 | MRC(手动引用计数) | ARC(自动引用计数) |
|---|---|---|
| 核心管理方式 | 开发者手动调用retain、release、autorelease,控制引用计数 | 编译器自动插入引用计数操作代码,开发者无需手动干预 |
| 底层核心 | 引用计数+SideTable,手动操作计数 | 引用计数+SideTable,编译器自动操作计数,Runtime协同 |
| 指针修饰符 | 主要用assign、retain、copy,无strong/weak | 主要用strong、weak、assign、copy(strong/weak为核心) |
| 常见问题 | 野指针、内存泄漏、过度释放(均由手动操作失误导致) | 主要是循环引用(编译器无法识别互相引用的场景),无过度释放问题 |
| 开发效率 | 低,需手动匹配retain/release,易出错 | 高,无需关注引用计数,编译器自动管理,降低出错率 |
| 适用场景 | iOS 5之前老旧项目、底层移植开发,目前已淘汰 | iOS 5及以上所有项目,当前主流开发方式 |
| dealloc重写 | 需重写,手动释放资源,必须调用[super dealloc] | 可重写,释放非对象资源(如通知、定时器),禁止调用[super dealloc] |
| 自动释放池 | 需手动管理autoreleasepool,临时对象需手动加入 | 编译器自动插入autoreleasepool代码,大循环可手动添加优化内存 |
五、面试高频问答(直接应答,无需修改)
-
问题1:ARC与MRC的底层核心是什么?有什么区别?
- 应答:核心都是引用计数,通过SideTable管理计数,遵循"谁持有谁释放"法则;区别核心是引用计数的管理方式------MRC手动调用retain/release,ARC由编译器自动插入相关代码,开发者无需干预,ARC解决了MRC手动操作易出错的问题,但仍需处理循环引用。
-
问题2:ARC中weak指针的底层实现原理?
- 应答:weak指针不持有对象,不改变引用计数;底层依赖Runtime的SideTable中的weak_table,存储对象的所有弱引用;当对象被销毁(引用计数为0)时,Runtime会遍历weak_table中该对象的所有弱引用,将其自动置为nil,避免野指针。
-
问题3:ARC中为什么会出现循环引用?如何解决?
- 应答:原因是两个或多个对象互相持有strong引用,导致引用计数无法降至0,对象无法销毁;常见场景是block与控制器、代理强引用、父子对象互相引用;解决方案是用weak指针打破循环,如block中用__weak修饰self,代理属性用weak修饰。
-
问题4:MRC的常见问题及解决方案?
- 应答:常见问题有野指针(对象释放后未置nil)、内存泄漏(忘记release)、过度释放(多次release);解决方案:对象释放后及时置nil,严格匹配retain和release操作,避免重复释放。
-
问题5:ARC与MRC混合开发需要注意什么?(延伸题)
- 应答:需通过__bridge系列关键字进行桥接,区分Core Foundation对象和OC对象;Core Foundation对象在ARC中需手动用CFRetain/CFRelease管理,避免内存泄漏;禁止在ARC文件中手动调用retain、release等方法。
六、面试总结(核心提炼,快速背诵)
-
底层核心:ARC与MRC底层均基于引用计数+SideTable,核心法则"谁持有谁释放",计数为0对象销毁;
-
核心区别:核心是"引用计数管理方式"------MRC手动操作,ARC编译器自动操作,ARC解决MRC手动失误问题,新增strong/weak修饰符;
-
面试重点:ARC的weak原理、循环引用(场景+解决方案),MRC的常见问题,二者核心区别对比;
-
实操关键:ARC开发需重点处理循环引用,MRC需严格匹配retain/release,混合开发注意桥接和Core Foundation对象管理。