# iOS 原生面试题精选
覆盖语言基础、系统原理、
UI、并发、架构、性能、安全、测试与发布。每题都有 30 秒速记、代码示例、详细解析和面试官追问,流程类题目配了时序图。
# 1 Objective-C 语言基础
# 1. [obj foo] 这一行在运行时到底发生了什么?找不到方法时还有哪几次补救机会?
⚡ 30 秒速记
[obj foo]编译后是objc_msgSend(obj, @selector(foo)),调用哪个函数到运行时才定 = 动态绑定- 查找顺序:
isa找到类 → 方法缓存cache→ 本类方法列表 → 沿superclass一路往上查,找到就写进缓存 - 找不到有三次机会:
+resolveInstanceMethod:动态加方法 →-forwardingTargetForSelector:换个接收者 →-methodSignatureForSelector:+-forwardInvocation:完整转发 - 三步都没接住 →
-doesNotRecognizeSelector:→unrecognized selector sent to instance崩溃 - 给
nil发消息不会崩,直接返回0/nil;这是特性,也是很多「怎么没反应」的 问题来源
[obj foo] 在编译期只是变成一次 objc_msgSend(obj, @selector(foo)) 调用,真正执行哪段代码,是运行时顺着 isa 去类里现查的。 先查方法缓存,命中就直接跳过去,这也是消息发送没想象中慢的原因;没命中再查本类方法列表、父类,一路查到 NSObject,找到后写进缓存。全链路都没找到,运行时还给三次机会:先问类要不要动态补一个方法,再问能不能把消息丢给别的对象,最后打包成 NSInvocation 让你自己处理。三次都不接,就是大家熟悉的 unrecognized selector 崩溃。
# 编译器把方法调用变成了什么
Objective-C 的方法调用不是 C++ 那种编译期就定好的虚表跳转,而是「发消息」。比如下面这行:
NSString *s = [user nameWithPrefix:@"Mr."];
// 编译后大致等价于:
NSString *s = ((NSString *(*)(id, SEL, NSString *))objc_msgSend)(user, @selector(nameWithPrefix:), @"Mr.");