最近这几年我一直在参与 iOS 候选人的面试,发现大部分人来面试前都背过不少题,但很多人扛不住追问。你问“weak 怎么实现”,他能答出来;你再问“为什么释放后会自动置 nil”,他就开始支支吾吾。这篇我不打算只丢一个题库出来,而是把自己当面试官时真实的提问思路、每个问题背后的考点、以及我会怎么根据候选人的回答继续往下挖,都尽量写清楚。标题既然敢写“可能最全”,我就不会只给你一张问题清单,而是会把 iOS 面试里最容易被追问的原理层、实战层和系统设计层一起拆开讲。
文章覆盖的范围会包括生命周期、内存管理、多线程、RunLoop、网络与安全、崩溃治理、系统能力适配,还有跨端混合开发里的 iOS 专属问题。不管是准备一二线大厂面试,还是想自己查漏补缺,这篇都可以作为一份长期维护的手册来用。
1. 面试官视角下的 iOS 知识体系,先分清这三个层面
1.1 基础层:考的是“能不能讲透”,不是“能不能背出”
基础题往往看起来很简单,比如“property 里的 atomic 和 nonatomic 有什么区别”。这种题如果只答“atomic 线程安全,nonatomic 不安全”,在面试官这里等于没答。因为 atomic 只保证原子性,不保证业务层面的线程安全。一个可变数组用 atomic 修饰,多个线程同时增删元素,照样会崩。真正满分的回答是先说明 atomic 底层会对 setter/getter 加自旋锁或互斥锁(不同版本实现有差异),再说明它锁的粒度很小,只保护属性的读写,不保护调用方的组合操作。
类似的还有 weak 和 strong 的区别、copy 与 strong 的区别。面试官问 weak 的时候,其实想听三个点:weak 不会增加引用计数;对象释放后 weak 指针会自动置 nil;这个自动置 nil 是通过 runtime 维护的 weak 表实现的。能答出这三层,就说明你不是只看了概念,而是看过 Runtime 相关的实现。copy 的问题则要分不可变对象和可变对象:用 copy 修饰 NSArray 时,传入一个 NSMutableArray,实际上会执行一次拷贝,得到不可变的 NSArray,避免外部继续修改导致数据错乱;但如果是一个已经不可变的 NSArray,某些系统实现下可能直接返回原对象,这种情况面试官一般不深究,可如果你能说清楚“拷贝是有优化空间的”,印象分会好不少。
基础层里还有一个高频考点是 KVC 和 KVO。只答“键值编码”和“键值观察”不够,要清楚 KVO 的实现是基于 isa-swizzling,动态生成一个子类并重写 setter 方法,在 setter 里发送回调。再延伸一点,为什么在 block 回调里拿到的 change 字典中 new 值有时候是空的?因为如果 KVO 注册时的 options 里没传 NSKeyValueObservingOptionNew,或者你在回调时机不对,就会拿不到。这种小细节反而是面试官区分背题党和实践党的关键。
1.2 原理层:从使用到源码,判断你有没有往下钻过
原理层不再问“怎么用”,而是问“底层是什么”。最常见的是 Runtime 相关的消息发送机制。面试官会问:给一个对象发消息,系统做了什么?这时候不能只说“调用方法”,而是要说出完整链路:objc_msgSend 找到对象 isa 指针,去方法列表中查找;找不到就去父类继续找;一直找不到时,会经历动态方法解析、消息转发等阶段。动态解析阶段用 +resolveInstanceMethod,转发阶段用 forwardingTargetForSelector 或者 methodSignatureForSelector 配合 forwardInvocation。这套链路背下来不难,难的是结合实际问题:如果想为一个没有实现的方法提供兜底,避免崩溃,该在哪个环节处理?大多数面试官期待答案是消息转发阶段,并且你能解释为什么不建议用这个方法来解决线上崩溃问题。
另一个常考的原理点是 isa 指针和类的结构。现在 Objective-C 的 isa 已经不是一个简单的类对象指针,而是采用了联合体来存储类信息、引用计数等,同时有 nonpointer 的优化。平时我们用 object_getClass 拿到的才是真正的类对象,直接访问对象的 isa 字段并不是稳妥的做法。这个问题能聊到 nonpointer isa,一般就说明你读过 Runtime 源码或者至少看过比较深入的文章。
自动释放池也是原理层的高频题。面试官会问 autoreleasepool 里的对象什么时候释放。很多人答“当前 runloop 循环结束”,但这个答案在 ARC 场景下并不完全准确。准确说,自动释放池里的对象会在 push 和 pop 之间延迟释放,主线程的 RunLoop 会在每次循环结束时自动 drain,所以给人的感觉是“一个循环结束后释放”。如果你自己手动创建了一个 autoreleasepool 作用域,那么出作用域时就会调用 pop,对象立即释放。能答到这个层次,面试官大概率会满意。
1.3 系统设计层:开放式题目怎么答才不被带偏
系统设计题不是 iOS 面试每一轮都有,但只要出现在二面或三面,基本就是分水岭。比较典型的题目是:让你设计一个图片缓存库,你会怎么做?这种题没有标准答案,但一定要有结构。首先说清楚分三层:内存缓存层、磁盘缓存层、网络请求层。内存缓存用 NSCache 而不是 NSDictionary,因为 NSCache 在内存紧张时会自动清理,而且支持成本限制和数量限制;磁盘缓存可以用文件系统存储,按 URL 哈希后作为文件名;网络请求层要处理并发、取消和优先级。
再比如“如何优化 App 启动速度”。这也是设计题里很高频的。回答时要先把启动过程拆开:进程启动、动态库加载、Runtime 初始化、AppDelegate 回调、首帧渲染。每个阶段都有对应方案,预编译、减少动态库、代码瘦身、冷启动时不执行过多非必要逻辑、首帧渲染用占位图。不要只答“延迟初始化”一个点,面试官要的是你有没有全局思维。
我还会问一类开放题:如果线上出现一个必现的崩溃,你会怎么排查。这个题可以聊得很长,正确的回答应当包含:拿到崩溃日志、看堆栈、符号化、根据调用栈定位到具体类;本地无法复现时就考虑加日志、利用 Xcode 的 Instruments、或者用 remote logging。如果是野指针或无法直接定位的内存问题,还要考虑开僵尸对象、Address Sanitizer、Malloc Stack 等工具。这道题我不想听候选人立刻报答案,而是想看他有没有一套系统的排查方法论,能不能从现象推理到原因。
2. 生命周期高频题:从 UIScene 到页面恢复,答得好很加分
2.1 从 UIApplicationDelegate 到 UISceneDelegate,到底变了什么
iOS 13 之后引入了 UIScene 生命周期,结果直到现在还有不少候选人只答 AppDelegate 那套。这里其实藏了一个很关键的问题:为什么系统要引入 UIScene?因为 iPadOS 支持多窗口后,一个 App 可能同时打开多个窗口,原来的 AppDelegate 粒度太粗,管不了单个窗口的前后台状态。所以 Apple 把“应用级事件”和“场景级事件”拆开,UIApplicationDelegate 负责进程生命周期、远程推送注册等,UISceneDelegate 负责每个窗口的状态变化。
面试中常见问法是:App 在后台回前台时,AppDelegate 和 SceneDelegate 的方法都会走吗?这要看场景连接是否还在。多数情况下会走 SceneDelegate 的 sceneWillEnterForeground 和 sceneDidBecomeActive。但如果是没有启用 Scene 的旧工程,仍然走 AppDelegate 的 applicationDidBecomeActive。能主动提到这两个路径的差异,面试官会觉得你确实处理过多窗口适配。
2.2 冷启动、热启动、进入后台、回前台,到底走了哪些方法
冷启动和热启动的区分不在于用户感受,而在于进程是否存在。冷启动时进程从零开始,要经过 main 函数、UIApplicationMain、加载主 storyboard 或 SwiftUI App 协议;热启动则是 App 已在后台,再回到前台时不重新创建进程,只走激活流程。面试时最容易答漏的是后台状态到前台状态时会先走 sceneWillEnterForeground 再走 sceneDidBecomeActive,而首次冷启动时并不一定有 sceneWillEnterForeground,通常是直接进入 active。
我有时会让候选人把冷启动完整链路按顺序说出来:main -> UIApplicationMain -> 加载 info.plist -> 创建 UIApplication 和 AppDelegate -> application:didFinishLaunchingWithOptions -> 创建 Scene/Window -> 首页视图控制器加载 -> viewDidLoad -> viewWillAppear -> viewDidAppear -> applicationDidBecomeActive。能一条线说下来,就证明他对启动过程有整体认知。接着我会追问:在这个链条里,如果想尽量提前首帧渲染,哪些步骤是可以后移的?这个问题结合启动优化就聊得深了。
2.3 ViewController 生命周期与数据恢复
ViewController 的生命周期问题单独拎出来也能出好几道题。比如 viewDidLoad 和 viewWillAppear 的调用时机差异:viewDidLoad 只会在控制器视图被加载到内存时调用一次,viewWillAppear 在每次即将显示时都会调用。很多人在 viewDidLoad 里做大量数据刷新,结果每次从二级页面返回时数据不更新,最后只能临时抱佛脚,把代码搬去 viewWillAppear。这就是基础不扎实的一个表现。
恢复状态也是面试官喜欢问的点:App 被系统 kill 后,怎么恢复用户之前的页面状态?iOS 提供了状态恢复机制,UIViewController 的 restorationIdentifier、encodeRestorableStateWithCoder 和 decodeRestorableStateWithCoder。但真正落地的 App 里,更多用的是本地缓存或者服务端状态同步。面试时可以两者都提,然后说清楚系统方案的限制在哪,比如系统不能在用户主动上滑关闭时恢复,这是机制设计上的明确行为。
2.4 输入框穿透这类 UI 异常,我会怎么现场出题
有一次我在面试里出了一道很基础的现场题:键盘弹起时,如果输入框被遮挡怎么办?候选人普遍能答出监听键盘通知、调整 contentInset 或 frame。但接着我又问:键盘收起后,输入框的位置没有回到原来的地方,底部出现一条空白,是什么原因?这个问题就涉及“输入框穿透”的变形。常见的坑是只调整了约束的 constant,没在键盘隐藏通知里恢复;或者使用了第三方键盘,高度获取时机不对导致动画错位。
更隐蔽的一个情况是 WebView 里输入框被键盘遮挡,这也是热搜词里经常出现的“输入框穿透”问题。原生层可以监听键盘高度再传给 WebView,但几百毫秒的动画时间如果没对齐,就会出现键盘已经弹起、WebView 的内容却还没来得及缩上去的“穿透感”。面试时如果你能主动把这个问题分成原生 UI 和 WebView 两种场景去答,就说明你不是只背了答案,而是真的见过线上问题。
3. 内存管理、RunLoop 与多线程,这几道题决定你有没有“内功”
3.1 ARC 实现原理:引用计数什么时候加一、什么时候减一
ARC 不是垃圾回收,它本质上是把手工内存管理的代码自动插入到正确的位置。编译期会分析对象的生命周期,在合适的地方自动调用 retain、release、autorelease。比如一个对象赋值给强引用变量时,ARC 会 insert retain;这个变量重新指向别的对象或者作用域结束时,ARC 会 insert release。虽然程序员感知不到,但在 MRC 时代这些都是手工操作的,面试中需要说清楚这一点。
weak 指针的实现离不开 runtime。对象释放时,objc_clear_deallocating 会从 weak 表中找到所有指向该对象的 weak 指针,把它们置为 nil。面试官如果继续深挖,会问 weak 表是用什么结构保存的。这是一个以对象地址为 key 的哈希表,value 是一组 weak 指针的数组。所以频繁使用 weak 不是没有成本的,它需要维护哈希表的读写锁,这也是为什么局部变量能不用 weak 就不用。
3.2 循环引用的 4 个经典场景,背下来不如理解它
循环引用几乎是必考。四个经典场景:Block、Delegate、NSTimer、CADisplayLink。Block 里如果强引用了 self,而 self 又持有这个 Block,就会形成环。但要注意,并不是所有 Block 都会造成循环引用,只有 Block 被对象持有时才有可能产生环。很多人无脑用 weakSelf,反而在一些不需要的场景里引入了不必要的复杂度。
delegate 之所以要用 weak,是因为很多情况下的持有关系是:子视图持有 delegate,而 delegate 可能是控制器,控制器又强持有子视图。如果 delegate 是 strong,就多了一个反向强引用,形成环。NSTimer 更麻烦,因为它被 RunLoop 持有,而 timer 又持有了 target(通常是 self),如果工程代码里把 timer 加到了 RunLoop,并且没有在适当时机 invalidate,即使你用了 weakSelf 也未必能解决,因为 Timer 在 iOS 10 之前对 target 是强引用,weakSelf 只是让 Block 里的 self 不回收,target 却依旧被强持有。这个问题能答到“iOS 10 前后 Timer 的 block 形式差异”,基本上就已经超出候选人的平均水平了。
3.3 GCD 死锁与线程之间的数据竞争
多线程这一块,我最喜欢问的第一题是:在主队列同步执行一个 block 会发生什么?答案是死锁。因为主队列是串行队列,主线程正在执行当前任务,又在等待新 block 执行,而新 block 排到队尾也必须等当前任务结束才能执行,于是互相等待。
接着我会上升到“队列和线程的关系”。很多候选人以为队列就是线程,这是误区。队列是任务的组织方式,线程是执行任务的载体。GCD 底层有自己的线程池,串行队列不意味着只有一个线程,而是所有任务按顺序执行,但从非主队列切回来时,你可能看到不同线程编号。理解这一点对排查线程爆炸问题很有用。还有一个常见的深挖点:DispatchSemaphore 的信号量超时处理。比如用信号量封装异步回调时,如果超时时间设置太长,会导致界面卡顿;如果太短,又拿不到真实验证结果。这种看似细节的问题,实际上反映候选人有没有在生产环境里踩过坑。
3.4 RunLoop 与界面卡顿监控
RunLoop 在面试里属于两极分化严重的题:背过的人能背出一堆 Source、Observer、Timer,但真正能结合实际问题的人很少。我会故意问:你平时是怎么监控页面卡顿的?最经典的方案就是在主线程 RunLoop 里加一个 Observer,监听 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting 之间的耗时。如果单次循环耗时超过阈值,就认为出现卡顿。这个问题既考了 RunLoop 的运行机制,又考了实际工程里的性能监控思路,一题两用。
RunLoop 的 mode 也需要搞清楚。界面滑动时 RunLoop 会切换到 UITrackingRunLoopMode,此时默认模式下的事件会被暂停。所以当你把 NSTimer 添加到默认 mode 时,滑动过程中 timer 不会触发。解决办法是把它加到 NSRunLoopCommonModes,或者基于 GCD 的 timer 来替代。面试官只要追问“为什么滑动时定时器不走”,就能快速判断候选人有没有真正使用过 Timer 而不是只背了定义。
4. 网络与安全:HTTPS 中间人、SSL Pinning、抓包
4.1 HTTPS 握手,别只答“用证书加密”
安全类问题也是 iOS 面试的重点,尤其是现在 App 的数据安全越来越受重视。面试官问 HTTPS 握手时,至少需要答出这几步:客户端先发起 ClientHello,服务端返回 ServerHello 并携带证书;客户端验证证书是否受信任;然后双方通过非对称加密协商出对称密钥;之后通信使用对称加密。只答“用证书加密”是没有区分度的。
追问方向一般是:客户端如何验证证书?这里要提到 CA 证书链、信任链、证书过期和吊销等。还有一个小细节是证书校验失败时的回调,这在 NSURLSession 的 challenge 回调里可以拿到,通过 completionHandler 传入 .performDefaultHandling 或 .cancelAuthenticationChallenge。有经验的人会补充一句:ATS 要求以 https 为主,但 ATS 不校验证书内容,只保证传输走 TLS,所以不能把 ATS 等同于安全。
4.2 SSL Pinning 的实现方式,以及为什么有人拿抖音做例子
SSL Pinning 是热词里出现的主题,很多候选人会在项目里用到,但说不清原理。Pinning 的核心意思是:客户端不只验证证书是合法的,还要验证证书是不是我所期望的那一张。通常有两种方式:证书锁定,直接对比服务端证书的公钥或者证书数据;公钥锁定,只对比公钥部分,这样证书轮换时只要公钥不变,就不需要发版更新。
我面试时会问:在 NSURLSession 的 challenge 回调里,你拿到的是一个 trust object,怎么判断是否 Pinning 成功?完整的步骤是取出服务器证书、从本地 bundle 读取内置证书,比较两者是否一致,或者提取 SecKey 来比较公钥。然后在热词里常出现的“抖音 iOS”例子,实际上是讨论抓包工具无法直接解密流量,因为 Ap p 做了 SSL Pinning,导致 Charles 抓包看到的只是 TLS 加密后的乱码。理解了这个例子,就自然会联想到调试时需要临时关闭 Pinning,或者使用 Frida、重签名等复杂手段。不过这类手段已经超出正常开发范围,我只是拿它来说明 Pinning 能有效对抗中间人攻击,并不建议在面试里过多讨论破解细节。
4.3 全链路抓包怎么操作,再聊网络层设计
说到抓包,面试官更想知道你有没有真实排查过线上网络问题。常见流程是先用 Charles 或 Wireshark 抓包,但 iOS 上要装证书并信任证书;如果要抓 HTTPS 明文,还要配置 SSL Proxying。如果抓到某次请求超时或者返回异常,再结合设备日志、后端日志一起定位。这里有个容易被忽略的问题:iOS 系统级的信任和 App 内部的信任是两回事。有些 App 在 AFNetworking/NSURLSession 上做了自定义证书验证,即使你在系统里信任了 Charles 证书,App 依然会拒绝连接,这就是上面讲到的 Pinning 在起作用。
网络层架构设计题也很常见。面试官会抛出一个场景:现有网络层完全裸用 NSURLSession,所有请求的逻辑散落在各个业务模块,怎么重构。我会期待候选人把它拆成若干层:底层是网络接口封装,负责 request 的组装、响应解析、错误处理;中间层是通用能力,比如统一缓存、日志上报、重试策略、网络状态监听;上层是业务 API 层,只负责把业务参数映射成网络参数。候选人如果能进一步提到请求优先级、取消机制、依赖注入这些点,已经算是优秀答案了。
5. 崩溃、启动速度与性能治理
5.1 常见崩溃类型,你是怎么定位的
崩溃治理是最能区分“开发经验”的考察项之一。常见崩溃类型有:数组越界、字典插入 nil、野指针、死锁、主线程卡死超过系统阈值被系统杀掉、内存占用过高被 jetsam 杀死。这些类型看起来简单,但每一条背后都有更深的点。比如字典插入 nil 并不是直接崩溃,而是在某些系统版本里会先执行 setObject:forKey:,然后因为 value 为 nil 而触发异常;数组越界则在 Debug 和 Release 下有不同表现,某些情况下编译器优化后还会出现奇怪的地址访问。
定位崩溃的方法也很关键。首先要会符号化:拿到 dSYM、用 symbolicatecrash 或 atos 还原调用栈。由于很多线上的崩溃堆栈是被系统扬掉的,没有业务堆栈,所以需要增加自己的监控逻辑,比如在启动时记录上个进程是否正常退出。面试官如果候选人一上来就说“看 crash log”,我通常会觉得太笼统,更希望听到他结合实际案例讲解排查过程。
5.2 启动优化:从“能跑”到“秒开”到底做了什么
启动优化在面试里已经算标配题。我对候选人的期望是至少能答出三个阶段:加载阶段、执行阶段、渲染阶段。加载阶段的主要优化是减少动态库数量、合并类/分类、使用静态库;执行阶段是减少 didFinishLaunching 中阻塞主线程的操作,把非必要初始化拆到后台线程或者懒加载;渲染阶段是优化首个控制器的 view 加载,比如用纯代码、减少 xib 层级。
追问的时候,我会抛出一个比较刁钻的问题:你能用 Instruments 或者 MetricKit 量出启动时间吗?这个问题能筛选出只停留在理论的人。正确的做法之一是用 Xcode 的 Organizer 查看启动时间分布,或者通过 MetricKit 拿到线上用户的启动耗时;也可以用环境变量 DYLD_PRINT_STATISTICS 打印动态库加载耗时。能对这些工具和技术名词如数家珍,说明他确实做过启动优化,而不是只会背方法论。
5.3 线上问题分析:无感埋点与卡顿监控
“无感埋点”这个词经常出现在热词里,实际指的就是对业务代码无侵入的监控方案。面试官问这个点,想要的思路通常是使用 AOP、Runtime method swizzling 或 fishhook 等机制,在统一入口处记录页面路径、接口耗时、点击事件。例如通过 method swizzling 替换 viewDidAppear 方法,在替换后的实现中插入页面事件统计,不需要每个页面写埋点代码。
要做到真正无感,还要注意 Crash 防护:swizzling 本身是高风险操作,如果替换的方法不存在或者父类方法被多个类重复替换,就会引发问题。我会建议候选人在面试里主动提到,优秀的无感埋点方案应当配合白名单、黑名单机制,并且要做防泄漏检查。这样既展示了方案设计能力,也展示了对稳定性的敏感度。
6. iOS 系统能力与权限适配,偏门题也可能突然出现
6.1 NFC 开发与权限申请,没踩过坑的人容易答错
NFC 是 iOS 系统能力里的一个有代表性的话题。开发前需要开启 Near Field Communication Tag Reading 能力,并在 Info.plist 中声明 NFCReaderUsageDescription,同时设置对应的 tag 类型。不要以为只用 Core NFC 就能扫描所有 NFC 卡片,系统对 NDEF 格式和非 NDEF 格式的访问权限完全不同。想做卡模拟几乎不可能,因为 iOS 对黑名单卡类型的限制非常严格,这点和 Android 有本质区别。
面试官如果继续问“为什么我的 NFC 读取有时候没反应”,常见原因包括:读取时需要把手机顶部靠近标签、App 必须在前台并且对应 session 正在运行、某些标签需要先解锁屏幕。这个问题没有太多底层原理,但能看出候选人有没有真正在真机上测试过。
6.2 开发者模式、证书、导出 IPA 文件
“iOS 开发者模式”和“导出 IPA 文件”也是热搜词里的常客。开发者模式在 iOS 16 之后变成了一项需要手动开启的设置,未开启时 Xcode 无法安装调试包到真机。面试中不会问得太细,但如果你要讲自动化打包,就一定会碰到证书、描述文件和导出方式。Apple 的证书体系里需要区分开发证书和分发证书,描述文件要与 App ID、设备列表、证书相匹配。导出 IPA 时可以选择 App Store Connect、Ad Hoc、Enterprise 或 Development,每一种对应不同的描述文件类型和安装范围。
这个知识点我会通过实际操作来考察,比如问:一台新设备需要加入 Ad Hoc 描述文件,但团队里证书已经失效,应该怎么处理?答案不只是“重新生成证书”,还要把之前的证书吊销、重新下载描述文件,并在 Xcode 里重新签名。能顺畅讲出来的人,一定是经历过发布流程的人。
6.3 后台音频与静音开关带来的播放问题
关于“微信小程序 iOS 静音状态下播放音乐”的话题,本质是 iOS 对音频会话的管理策略。iPhone 侧面的静音拨片只对音频重放有效,但很多情况下 App 的音视频播放会受影响。原生开发可以通过 AVAudioSession 设置分类来控制是否受静音键影响,比如设置 AVAudioSessionCategoryPlayback 时,App 可以在静音开关打开的状态下继续出声。但网页或者小程序里的 audio 组件,默认行为可能受浏览器限制,导致静音开关一打开就无声。
面试中如果遇到这种问题,我会用追问的方式让候选人说清楚 AVAudioSession 的几种主要 category 以及它们的区别:Ambient 表示跟随静音键,Playback 表示忽略静音键,Record 表示录音优先。能够把选项和实际行为对应起来,比背概念有用得多。
6.4 自动化测试与虚拟摄像头这类偏门工具
自动化测试在面试中通常不会作为单独主题,但会作为加分项。比如 XCUITest 可以用来跑 UI 自动化,Appium 也可以驱动 iOS 设备。热词里的“iOS 自动化”和“iOS 虚拟摄像头”,其实都属于测试辅助工具范畴。虚拟摄像头主要是通过模拟视频源,帮自动化脚本稳定地输入摄像头画面,原生 XCUITest 里不太好直接做,需要用到系统级的虚拟摄像头驱动或硬件方案。面试时能谈到这个方向,说明接触过视频类 App 的自动化,是一个亮点。
不过我不建议候选人在面试中堆砌工具名。更好的策略是结合一个真实场景:你如何保证直播类 App 的推流功能在回归测试中不阻塞?如果有人能提出用虚拟摄像头喂入模拟画面、通过自定义协议断言推流开始和结束,同时用 XCTest 定时截图比对 UI 状态,这个回答就非常有含金量。
7. 跨端与混合开发场景下的 iOS 特定问题
7.1 uni-app 打包 iOS 的流程、证书和费用
跨端开发已经成为很多团队的常态,所以面试题也会延伸到“uni-app 打包 iOS 是否收费”这类实际操作上。uni-app 是一套跨端框架,开发阶段可以免费使用 HBuilderX,但真正打包 iOS 需要 Apple 开发者账号,这个账号是 Apple 收费的。还有一个容易被忽视的问题:uni-app 云打包可以选择公共证书或自定义证书。公共证书打包出来的 App 不能上架 App Store,也不能用于企业分发,只能用来调试。如果要上架,必须在 Apple Developer 后台生成正式的发布证书和描述文件,然后上传到 DCloud 或本地用 Xcode 打包。
我会接着追问:uniapp 项目中如何解决原生插件和 iOS 权限描述的关系。里面涉及 manifest.json 中声明的权限文案,必须和苹果审核要求一致。很多时候 App 被拒的原因不是功能有问题,而是权限描述不具体。这类问题虽然简单,但能看出候选人有没有真正走完上架流程。
7.2 BLE 低功耗蓝牙在 iOS 上的连接坑
热搜词里出现“flutter 低功耗蓝牙 ios 有问题嘛”,以及“uni-app BLE iOS 可以根据蓝牙的 deviceid 建立连接吗”,说明跨端开发里 BLE 的问题非常高发。先说基础:iOS 的 CoreBluetooth 框架里,peripheral 对象有 UUID 标识。但这个 UUID 在 iOS 上通常会随 App 重新安装或系统重置而变化,不能像 Android 那样把它当作固定的设备地址。所以如果你发现连不上,很可能不是代码问题,而是 UUID 变了。
另一个高频坑是 BLE 后台模式。iOS 需要开启相应的 background mode,并且即便开启了,系统也不会让 App 在后台长时间运行。很多跨端框架在 Android 上表现正常,到了 iOS 上一灭屏就掉线,就是这个原因。面试中回答这类问题,不能只讲框架 API,要结合系统限制来解释现象。
7.3 WebView、小程序与原生交互的常见雷区
iOS 面试里关于 WebView 的问题,通常集中在 JS 和原生通信。早期的 UIWebView 只能用 JavaScriptCore 或者拦截 URL Scheme,后来 WKWebView 提供了 WKScriptMessageHandler,可以让 JavaScript 直接通过 window.webkit.messageHandlers 调用原生方法。面试官爱问两者的区别,以及为什么 UIWebView 被淘汰。除了性能和内存问题,UIWebView 的内存泄漏极其严重,这是一个很重要的淘汰原因。
小程序相关的“静音播放音乐”问题、输入框穿透问题,在前面已经聊过,本质上都是 Web 技术栈承载在 iOS 系统之上产生的摩擦。如果候选人能把这几个问题串起来,讲清 WebView、小程序、WKWebView 配置之间的关系,那我基本可以认定他有混合开发经验,不是只会写原生。
这里我补充一个自己的习惯:平时我会随身维护一份面试题库,把候选人答不出来的问题按“是否值得考下一次”分类。做这个总结的初衷也一样,面试题永远在变,但背后的知识点是稳定的。只要你能把每个题目落到“为什么这么设计”而不是“这个答案是什么”,不管面试官怎么追问,你都不会太被动。希望这份整理能成为你长期参考的一份手册,而不是面试前临时抱佛脚的清单。