iOS 开发 ARC 与 MRC 底层原理及区别

核心说明:聚焦面试高频提问,围绕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等方法。

六、面试总结(核心提炼,快速背诵)

  1. 底层核心:ARC与MRC底层均基于引用计数+SideTable,核心法则"谁持有谁释放",计数为0对象销毁;

  2. 核心区别:核心是"引用计数管理方式"------MRC手动操作,ARC编译器自动操作,ARC解决MRC手动失误问题,新增strong/weak修饰符;

  3. 面试重点:ARC的weak原理、循环引用(场景+解决方案),MRC的常见问题,二者核心区别对比;

  4. 实操关键:ARC开发需重点处理循环引用,MRC需严格匹配retain/release,混合开发注意桥接和Core Foundation对象管理。

相关推荐
盏灯3 小时前
以前有一个同事说:最讨厌下班提需求又没电脑在身边...
前端·后端·面试
唐诺4 小时前
iOS 与 Xcode 版本差异指南
ios·cocoa·xcode
AI人工智能+电脑小能手4 小时前
【大白话说Java面试题】【Java基础篇】第39题:说说反射的用途及实现原理,Java获取反射(Class)的三种方法
java·开发语言·后端·python·面试
研究点啥好呢7 小时前
Momenta后端开发面试题精选:10道高频考题+答案解析(数据产线方向)
c++·python·面试·求职招聘
李日灐8 小时前
【优选算法5】位运算经典算法面试题
后端·算法·面试·位运算
MonkeyKing8 小时前
iOS dyld加载流程与App启动原理(pre-main阶段)详解
ios
MonkeyKing8 小时前
iOS类加载全解析:map_images、load_images、initialize调用时机
ios
ayqy贾杰8 小时前
过去三年我做对了一件事
前端·面试·ai编程
Raink老师9 小时前
用100道题拿下你的算法面试(链表篇-5):删除链表的倒数第 N 个节点
算法·链表·面试