以下是整理的中高端 iOS 面试题答案
2025-06-05更新一下: 1、需要了解 UIView 和 CALayer 的关系。 2、需要了解堆和栈的关系。对象创建后是在堆上还是栈上,什么时候发生变化。
内存管理
1. 什么是引用计数?Objective-C 是如何通过引用计数管理内存的?
引用计数是一种内存管理技术,每个对象维护一个计数器,表示有多少引用指向它。当计数降为 0 时,系统自动释放对象。Objective-C 使用 retain 和 release 方法来增加或减少引用计数,ARC(Automatic Reference Counting)则自动插入这些调用,简化了内存管理。
2. ARC 和 MRC 的区别是什么?ARC 的工作原理是什么?
ARC(自动引用计数)由编译器在编译时插入 retain、release 和 autorelease,开发者无需手动管理。而 MRC(手动引用计数)需要显式调用这些方法。ARC 不改变引用计数的基本原理,而是让编译器替你完成了繁琐的内存管理工作。
3. 在 ARC 环境下,什么情况下需要使用 __weak 和 __strong?
__strong 是默认的修饰符,表示强引用,适合一般情况。__weak 用于打破循环引用,例如在 block 内引用 self 时,避免 block 和 self 互相持有导致内存泄漏。
4. __autoreleasing 和 __strong 的区别是什么?
__strong 表示一个强引用,自动管理生命周期,直到引用置 nil 时对象才会释放;__autoreleasing 用于函数中传递返回值,表示临时引用,返回后由 autoreleasepool 自动释放。
5. 什么是 __block 修饰符?它在 ARC 环境下有什么作用?
__block 允许 block 内部修改外部变量的值,在 ARC 环境下,它会自动强引用该变量并在 block 完成后释放,从而避免内存问题。
6. 如何判断对象是否已经释放?会不会有野指针问题?
ARC 环境下,__weak 指针在目标对象释放时会被自动置为 nil,因此一般不会出现野指针问题。如果是 unsafe_unretained 或 assign 修饰的对象,则可能出现野指针,需要谨慎使用。
7. assign、retain、copy 和 weak 的区别是什么?
• assign:直接赋值,适用于基本数据类型。
• retain:强引用,引用计数 +1(MRC 中使用)。
• copy:创建对象副本,用于不可变对象。
• weak:弱引用,目标释放时自动置 nil。
8. copy 和 mutableCopy 的实现原理是什么?它们的区别有哪些?
copy 是浅拷贝,对于不可变对象,返回的是指向相同内容的副本;对于可变对象,copy 返回不可变副本。mutableCopy 总是返回一个可变副本。它们的实现依赖于对象的类实现的 copyWithZone: 和 mutableCopyWithZone: 方法。
9. 如何避免循环引用(retain cycle)?在什么情况下会发生?
循环引用通常发生在对象相互持有时,如 A 持有 B,B 也强引用 A。可以通过将其中一个引用改为 weak,或者使用 __block修饰符来打破循环。
10. Autoreleasepool 的作用是什么?什么时候需要手动创建一个 Autoreleasepool?
Autoreleasepool 延迟释放 autorelease 的对象。在批量创建对象时,手动创建 Autoreleasepool 可降低内存峰值:
@autoreleasepool {
for (int i = 0; i < 1000; i++) {
NSString *string = [[NSString alloc] initWithFormat:@"%d", i];
}
}
Runtime
11. 什么是 Runtime?它的主要功能有哪些?
Runtime 是 Objective-C 的运行时库,提供消息发送、动态方法解析、类和对象的动态创建与销毁等功能。它实现了面向对象语言的动态特性,使得方法调用、消息转发和类型信息查询等操作可以在运行时进行。
12. 如何使用 Objective-C Runtime 动态添加一个方法?
可以使用 class_addMethod() 动态添加一个方法。例如:
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
通过为 cls 添加 name 对应的 imp(即函数实现),在运行时为类添加新方法。
13. 什么是 Method Swizzling?它的应用场景有哪些?
Method Swizzling 是交换两个方法实现的一种技术。常见应用场景包括:
• 为系统类添加行为(如统计 UIViewController 的 viewDidLoad 调用次数)。
• 替换系统默认实现(如替换 UILabel setText: 方法添加自定义逻辑)。
14. KVO 的底层实现原理是什么?为什么需要动态生成子类?
KVO 通过 Runtime 动态生成一个子类,并将观察的对象的 isa 指针指向这个子类。子类会重写属性的 setter 方法,在值变更时调用 willChangeValueForKey: 和 didChangeValueForKey:,从而通知观察者。
15. KVC 和 KVO 有什么关系?它们各自的工作原理是什么?
KVC(Key-Value Coding)是直接通过键访问对象的属性。KVO(Key-Value Observing)依赖 KVC,当某个键的值通过 KVC 修改时,会触发 KVO 的通知机制。
16. 如何动态添加属性到一个类中?实现原理是什么?
可以通过 Runtime 的关联对象(Associated Objects)实现:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
实质是在对象外部维护一个关联表,将键值对和对象关联起来。
17. isa 指针的作用是什么?为什么 isa 指针需要优化?
isa 指针指向对象的类,表明该对象的类型信息。64 位系统中,isa 被优化为一个 bit field,能够存储更多的元信息,如引用计数、是否正在 dealloc、弱引用清理标记等。这种优化提高了内存利用率和访问效率。
18. Objective-C 中的消息发送流程是怎样的?objc_msgSend 是如何工作的?
objc_msgSend 是方法调用的核心。当发送消息时,Runtime:
• 通过 isa 找到类。
• 在类的方法缓存中查找方法。
• 找不到时沿继承链向父类查找。
• 最终通过 IMP(方法实现的指针)执行方法。
19. 如果方法找不到,Runtime 会做什么?如何实现动态方法解析?
如果方法未实现,Runtime 会:
1. 调用 resolveInstanceMethod: 让类动态添加方法。
2. 调用 forwardingTargetForSelector: 转发给其他对象。
3. 进入完整的消息转发流程:methodSignatureForSelector: + forwardInvocation:
20. 如何遍历一个类的所有方法和属性?需要用到哪些 Runtime 函数?
可以通过 Runtime 提供的函数遍历:
• 获取方法列表:class_copyMethodList
• 获取属性列表:class_copyPropertyList
• 获取实例变量列表:class_copyIvarList
21. 动态方法解析与消息转发的区别是什么?
动态方法解析(Dynamic Method Resolution)发生在方法调用失败后,通过 resolveInstanceMethod: 或 resolveClassMethod: 尝试动态添加方法实现。
消息转发(Message Forwarding)则是当动态方法解析失败后,通过 forwardingTargetForSelector: 或 forwardInvocation: 将消息转发给其他对象或自定义处理。
22. 如何用 Runtime 创建一个新类?
使用 objc_allocateClassPair 创建类,使用 class_addMethod、class_addIvar 添加方法和实例变量,最后调用 objc_registerClassPair注册该类。
RunLoop
23. RunLoop 的工作原理是什么?
RunLoop 是一个事件处理循环,它会等待输入事件(如触摸、定时器、网络事件),并以适当的方式分发事件。它通过多次检查事件源(Source)、观察者(Observer)、定时器(Timer),然后调用对应的回调来处理。
24. 为什么 NSTimer 默认会受 RunLoop 的模式影响?
NSTimer 默认运行在 NSDefaultRunLoopMode,当 RunLoop 切换到 UITrackingRunLoopMode(如滑动 UIScrollView 时),NSDefaultRunLoopMode 下的 Timer 就不会被触发。
25. 如何在后台线程中使用 RunLoop?
子线程的 RunLoop 不会自动启动。需要手动启动:
[[NSRunLoop currentRunLoop] run];
同时至少添加一个输入源(如 Timer 或 Port)以让 RunLoop 保持运行。
多线程
26. 为什么主线程需要 RunLoop,而全局并发队列不需要?
主线程需要处理 UI 事件、用户交互和定时器等,需要一个运行循环来等待和分发事件。全局并发队列的线程通常用于一次性执行任务,不需要持续运行,因此无需 RunLoop。
27. 在主线程上使用 dispatch_sync 会发生什么?
如果在主线程调用 dispatch_sync(dispatch_get_main_queue(), ^{ ... }) 会导致死锁。因为 dispatch_sync 会等待 block 完成,但 block 必须在当前队列完成其他任务后执行,造成循环等待。
28. 如何检测和避免死锁?
• 避免同步调用自身队列。
• 使用 Xcode 的 Thread Sanitizer 工具检测死锁。
• 将耗时操作移到其他队列,减少主线程阻塞的可能性。
29. NSLock、pthread_mutex 和 dispatch_semaphore 的区别是什么?
• NSLock 是基于 Objective-C 的封装,使用简单,但性能略低于其他选项。
• pthread_mutex 是 POSIX 标准的底层锁,性能高,适合高频使用。
• dispatch_semaphore 不是真正的锁,而是通过信号量控制并发访问,更轻量。
30. 在 Objective-C 中如何实现线程安全的懒加载?
使用 dispatch_once 确保初始化代码只执行一次:
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
UI 和动画
31. UIView 的动画是如何在底层实现的?
UIView 的动画是基于 Core Animation 实现的。
当调用 UIView 的动画方法(如 animateWithDuration:animations:)时,实际是对 Layer 的属性做了隐式动画。Core Animation 会处理动画帧、时间曲线、渲染等细节,UIView 只是在高层提供了易用的接口。
32. 如何提高复杂动画的性能?
• 避免频繁调用 setNeedsDisplay;
• 使用 rasterization 缓存复杂图层;
• 减少透明度图层的数量;
• 使用 shouldRasterize 让静态内容提前渲染成位图。
33. 如何在 UITableView 上实现流畅的滚动性能?
• 减少 cellForRowAtIndexPath: 中的复杂逻辑;
• 复用 cell 减少内存分配开销;
• 异步加载和缓存图片资源;
• 将图像解码放在后台队列完成,避免主线程卡顿。
性能优化
34. 如何找到内存泄漏并解决?
• 使用 Instruments 的 Leaks 工具;
• 检查循环引用;
• 检查未释放的强引用;
• 使用 Xcode 的 Memory Graph 调试器查看对象引用链。
35. App 启动时间过长的原因可能有哪些?
• 在 didFinishLaunchingWithOptions: 中执行了耗时任务;
• 初始化过多的类或加载过多的资源;
• 网络请求阻塞了启动流程。
36. 如何优化 App 的冷启动时间?
• 延迟初始化不必要的组件;
• 减少动态库加载数量;
• 缩小资源的初始加载量;
• 优化数据解码和解析流程。
综合问题
37. 如何为 Objective-C 项目设计一个模块化架构?
将不同的功能模块拆分成独立的框架或动态库,每个模块只负责自己的逻辑。通过公共协议或接口定义模块间的通信,确保模块之间的依赖最小化。
38. 如何保证数据模型与视图的解耦?
使用 MVC 或 MVVM 架构。将数据逻辑(Model)独立出来,视图只负责展示,通过 Controller 或 ViewModel 将两者连接起来。
39. 如何管理一个大型项目中的依赖?
使用 CocoaPods 或 Carthage 管理第三方库;
通过模块化拆分功能,每个模块的依赖独立管理;
明确依赖的版本和来源,确保团队一致性。
40. 如何在 Objective-C 项目中引入 Swift?
• 添加 Swift 文件时,Xcode 会自动生成 Bridging Header;
• 在 Bridging Header 中引入需要在 Swift 中使用的 Objective-C 头文件;
• 使用 @objc 暴露 Swift 方法给 Objective-C 调用。