老实说,收到这份“2023年度小满春招iOS研发岗第一批笔试”的邮件时,我多少有点意外。春招笔试我见过不少,绝大多数是牛客网上的选择题加两道算法题,做完就等通知。小满这批不一样,它把笔试题和实际工程场景绑得很紧,考的不是“你背过多少”,而是“你排过多少坑”。我花了整整一个下午做完,出来之后第一反应不是累,而是想找人对一遍答案。
这份笔试覆盖的面很广,从OC底层到App上架,从蓝牙连接参数到uniapp打包流程,几乎把iOS研发日常要碰的链路都过了一遍。如果你正准备iOS春招,或者社招想找一份能真正练手的笔试题,这篇复盘应该能帮你省不少事。我会按我的做题顺序,把每一类题型的考察意图、我的解题思路、以及我栽过跟头的地方都写出来,也尽量把底层原理讲透,毕竟光知道答案,面试官追问一句就露馅了。
1. 笔试定调:这批题到底想考什么
先把整体观感放在前面。这套笔试试卷并不是单纯的知识点堆积,而是围绕一个iOS研发工程师从开发到上线再到维护的完整闭环来出题。我做了个分类统计,题型结构大概是下面这样:
| 题型 | 占比 | 重点考察方向 |
|---|---|---|
| OC/Swift基础与内存管理 | 25% | weak底层、引用计数、RunLoop |
| 多线程与性能优化 | 20% | GCD死锁、卡顿检测、电池优化 |
| 系统机制与UI布局 | 20% | 蓝牙状态机、BLE参数、UIStackView、分屏适配 |
| 工程化与打包上架 | 20% | 开发者证书、加急审核、IPATest流程、抓包 |
| 混合开发与开放性设计 | 15% | uniapp/混合架构选型、自动化测试方案 |
从出题逻辑来看,这份卷子很明显在筛选两类人:一是基础扎实、能解释“为什么”的科班选手;二是真正上线过产品、踩过发布链路坑的实战型选手。纯刷题选手和纯业务选手都容易被筛掉。
有一个细节值得注意:题目里多次出现了面向工程落地的场景,比如“uniapp打包iOS测试包全流程”“iOS开发者App证书更新”“charles iOS抓包”这类关键词。这说明出题人默认你至少完整走过一次上架流程,而不是只会写页面。如果你还没拿到过真机调试证书,建议先花时间把开发者账号、描述文件、导出IPA这一套流程走通,否则笔试中这部分基本就是空白状态。
另外,这套题对系统机制原理的挖掘比一般笔试深得多。比如蓝牙那部分,它不直接问“怎么用CoreBluetooth”,而是问“CBPeripheralManager系统级蓝牙状态和App级蓝牙状态能否区分”,这种题如果不看WWDC的Session,只靠日常API调用经验,很容易答偏。我后面会单独用一大节展开。
2. 技术深度题:内存、并发与卡顿的真实考点
2.1 weak底层原理与“无效weak引用”陷阱
这批题里有一道让我印象很深:问的是“weak修饰的对象在什么情况下不会被置为nil”。大多数人都知道weak的底层是SideTable里的weak_table,对象释放时会遍历weak_entry_t把指针清空。但题目加了个场景:对象在AutoreleasePool中被强引用,当前作用域结束后weak指针不会立刻变nil。
这就考到了objc_storeWeak和objc_loadWeak的调用时机,以及AutoreleasePool的push/pop边界。如果只答“weak自动置nil”就踩坑了。我在答题时写了这样一段推理:weak置nil的本质依赖objc_destructInstance执行完release后,runtime通过synchronized对象列表遍历弱引用表。只要对象还有一份强引用在AutoreleasePool栈上持有,dealloc就不会触发,weak当然不会变nil。这个点我建议大家在复习时配合objc4源码看一遍,尤其是weak_clear_no_lock函数的条件判断。
2.2 RunLoop和性能监控的联动考法
关于卡顿监控,笔试没有直接问“怎么监听卡顿”,而是问“卡顿检测的runLoopObserver应该在哪个activity中监听,为什么有人用beforeWaiting而非afterWaiting”。这个差距很关键。afterWaiting触发时机是RunLoop刚被唤醒,此时还没处理事件,如果源事件本身耗时很低,但系统主线程已经积压了一堆任务,afterWaiting的耗时并不能真实反映用户感知的卡顿。beforeWaiting则是在处理完所有事件即将休眠前回调,能反映出这一轮RunLoop迭代的完整耗时,所以主流卡顿监控SDK都用beforeWaiting。
题目还延伸到了离屏渲染和电池优化。有一个小题是问“为什么圆角+阴影同时设置会导致掉帧”。我在答题时拆了两层:圆角会造成离屏渲染,阴影是另一个离屏上下文,两个加一起就是多重离屏,会赶在提交前把所有layer合成到backing store,而Apple的渲染树提交是按事务批处理的,只要有一个图层触发离屏,整个图层树可能都要进入offscreen pass。更合理的做法是给shadowPath和cornerRadius分开表达,或者直接使用maskToBounds并避免同时有透明边框。这块如果你平时只调UI,没有在Instruments里看过Core Animation的kFPS,理解起来会有点抽象,但面试官很青睐这种能从渲染管线层面解释的回答。
2.3 多线程同步:从死锁到信号量
这套题的多线程部分没有考很偏的函数,经典的“同步任务在串行队列里调用sync会不会死锁”依然出现了。但真正的分水岭在于后面还跟了一道综合题:在多个并发网络请求都完成后统一刷新UI,要求同时给出GCD和OperationQueue两种方案。GCD用DispatchGroup加notify,OperationQueue用最大并发数加依赖关系都可以。不过题目埋了个坑:如果某个请求超时,两种方案默认行为不同,DispatchGroup会等超时结束才走notify,OperationQueue只要被依赖的Operation被标记finish就行。题目问怎么处理超时兜底,我答的是封装一个带timeout的异步闭包,内部用DispatchSourceTimer做超时驱动,避免因为某个请求挂死导致整个页面一直loading。
这类题目考察的本质不是API记忆,而是对“任务状态机”的理解。答这类题,建议把同步、异步、串行、并行、栅栏、信号量这几个基础概念画成图想一遍,然后结合平时的网络层封装来回答,比死记结论更有说服力。
3. 系统机制与UI题:从蓝牙状态机到分屏适配
3.1 系统级蓝牙状态与App级蓝牙状态:到底能不能区分
这是整份卷子我个人最喜欢的一道题:“CBCentralManager系统级蓝牙状态和App级蓝牙状态能区分出来吗?”先说结论:能,但要看系统版本。
iOS 13之前,CBCentralManager的state属性只有系统级含义,App没有单独的授权状态。iOS 13开始,蓝牙权限被拆成系统蓝牙开关和App授权两个维度:系统开关关闭时centralManagerDidUpdateState拿到的是poweredOff;App被用户关闭权限时,拿到的是unauthorized,并且BLE授权状态和定位权限一样,有“使用期间允许”这类细分。所以题目的标准答案是:能区分,分别对应CBCentralManagerState的poweredOff和unauthorized。
我在答题时还额外补了一个容易被忽略的点:CBPeripheralManager的state和CBCentralManager的state并不完全一致,TM类的外设管理器有自己的授权体系,如果你把这两个混用,会出现“中心模式正常但外设模式回调权限拒绝”的诡异现象。做蓝牙外设的,调试时建议把两套状态分开打日志。
3.2 BLE连接参数规范:连接间隔、从机延迟和超时
这道题给了一个嵌入式设备场景:手环设备连接后,每20ms发一次数据,但app端偶尔出现数据断流。问怎么调优BLE连接参数。我在答题时按连接参数表和实际经验分了三块:
- 连接间隔(Connection Interval):单位1.25ms,一般建议在15ms到30ms之间平衡功耗和吞吐量。如果设备需要高频传输,比如心率波形,可以申请7.5ms的最小间隔,但系统可能因为射频共存而拒绝,实际协商结果会落在30ms左右。
- 从机延迟(Slave Latency):允许设备跳过一定数量的连接事件,常用于省电。如果从机延迟配置过大,app端会感觉数据到达不稳定。手环场景建议设0,否则明明发了数据但主机端几个连接事件后才收到。
- 超时时间(Supervision Timeout):范围100ms到32s,必须大于连接间隔乘以(1+从机延迟)的2倍。很多断连问题不是信号问题,而是这个参数配置得不合理。
笔试改卷时阅卷人会看你能不能把参数落实到具体业务场景,而不只是背规格。所以我写了“20ms上报数据建议连接间隔15ms、从机延迟0、超时5s起步,再根据功耗测试收敛”这样一段,让答案显得有决策依据。
3.3 UIStackView与分屏适配
UIStackView出现很多年了,但笔试考得不浅。题目问“UIStackView里嵌套UIStackView,在iOS分屏宽度变化时如何保持比例布局”。很多人觉得UIStackView会自动搞定一切,其实它有坑:当从Regular宽度变到Compact宽度,StackView可能无法同时满足多个约束,这时默认会压缩或拉伸,而不是按你设定的distribution优先级走。
我在答题时画了根因:UIStackView本质是懒约束生成器,它的布局信息在触发layout时才会生成约束,系统根据排列方向和distribution统一生成等宽、等比或间距约束。如果父视图宽度变化太快,中间未满足的约束会临时参与计算,出现闪烁。解决方法是给StackView里的视图设置明确的Content Hugging和Content Compression Resistance优先级,而不是只依赖StackView的distribution。另外,分屏时建议用traitCollectionDidChange或者viewWillTransition监听SizeClass变化,给约束做一次刷新。这题真做过的同学应该都有共鸣,尤其是当StackView嵌套三个子视图、其中一个子视图还有固定宽高比时,分屏表现真的会和人预期不一样。
3.4 动态更换App图标:setAlternateIconName的边界问题
笔试题里出现了“UIApplication.shared.setAlternateIconName本身调用系统级确认弹框时报错”的场景。这个点确实有点偏,但这两年越来越多人做节日换肤功能,所以也拿上台面了。我答题时写的是:系统弹框属于进程外系统UI,不会随App激活状态变化,但调用这个API时必须保证App处于活跃状态,不然会抛Error。如果同时配置了多个Alternate Icon,需要在Info.plist里把CFBundleAlternateIcons配置对,否则即使方法名没错,也会因为找不到图标资源而失败。更隐蔽的是,iOS 26之后这个API的行为可能有微调,系统弹框的样式和回调时机也与旧版本不同,建议在真机上充分回归。
4. 工程化链路题:证书、上架、抓包与打包全流程
4.1 iOS开发者App证书更新的完整链路与常见失败点
笔试工程化部分的第一道题就是证书更新。说实话,这道题对没上过架的人很不友好,因为Apple开发者后台的“证书、标识符和描述文件”入口不是字面上那么简单。我按自己平时的操作流程拆解如下:
- 登录Apple Developer后台,进入Certificates, Identifiers & Profiles。
- 在Keychain Access里从“证书助理”请求证书,生成CSR文件,这个文件是私钥归属的关键,私钥一旦丢失,对应证书即使下载成功也无法安装签名。
- 上传CSR,选择iOS Distribution或iOS Development,下载证书双击安装到钥匙串。
- 更新App ID配置,确认Bundle Identifier与工程完全一致。
- 修改或新建描述文件,选择对应的App ID和证书,下载双击安装。
- 在Xcode的Signing & Capabilities里重新选择Team和Provisioning Profile,必要时执行一次Clean Build Folder。
常见失败点有两类:一是老证书没在开发者后台吊销,新证书和旧证书同时存在,导致Xcode签名时选了旧证书;二是描述文件里包含的证书列表过期,真机调试时提示“No valid signing certificate found”。笔试有一问专门考这个,我给的答案是:签名匹配是证书+私钥+描述文件三者的组合关系,缺一不可,Xcode提示unable to sign时,优先检查钥匙串里是否有对应的私钥。
4.2 加急审核申请与上架时间线
关于上架,题目不是问审核流程,而是问“什么情况下可以用加急审核(App Review Expedite)”。很多人的第一反应是“应用被拒后急着上线”,但Apple一般不接受这种理由。加急审核的真实适用场景是:应用涉及在线举办的时效性活动、支付通道即将过期、或者存在严重的线上漏洞需要紧急修复。申请时要在Request Accelerated App Review里说明业务影响、提供证据截图,并给出可复现步骤,并最好在提交申请后打客服电话同步工单号。
我在答题时补充了自己的经验:加急审核不等于免检,甚至可能因为信息不完整被快速拒绝。如果时间真的非常紧,同时建议准备TestFlight外部测试作为临时分发渠道,但不代表能绕过App Store审核,只是让真机验证和业务演示不阻塞。
4.3 Charles抓包与HTTPS解密
这道题要求描述“charles iOS抓包”的完整过程。我按环节拆分:
- 准备:Mac端安装Charles,开启Proxy Settings里的SSL Proxying,并勾选Include位置添加*:443。
- iOS端:WiFi手动代理指向电脑IP和端口8888,初次抓包会提示安装证书,需要在Safari打开chls.pro/ssl下载并安装到描述文件。根证书安装后,还要在“设置-通用-关于本机-证书信任设置”里把Charles证书设置为完全信任。
- App侧:如果App做了SSL Pinning,直接抓包会失败。需要区分是证书锁定还是公钥锁定。证书锁定可以在Charles里换用Dynamic SSL Proxying加证书替换;公钥锁定只能通过Hook或重打包绕过,这个放到逆向层面讨论。
笔试题有个延伸问题:真机抓包连不上代理可能是什么原因?我列了三点:电脑防火墙拦截、同一WiFi但AP隔离、iOS的“本地网络”权限未打开。最后一个最容易被忽略。
4.4 uniapp打包iOS测试包全流程
小满这批题出现uniapp相关题目,也在意料之中,现在不少公司都是原生和跨端混用。题目是让简述“uniapp iOS app打测试包全流程”,我是按真实操作顺序写的:
- 用HBuilderX云打包或本地离线打包。云打包需要先在manifest.json里配置AppID、Bundle Identifier,并上传打包证书和描述文件。
- 如果选择离线打包,用Xcode打开SDK里的工程模板,将uni-app生成的wgt资源或完整前端资源导入工程。
- 证书配置与原生App一致:开发阶段用Development证书,测试包通常走TestFlight或蒲公英等分发平台。
- 测试包打出来后,如果只有企业证书或者个人开发者证书,无法直接通过扫码安装到未注册的UDID设备,要么加设备UDID到描述文件,要么用TestFlight。
这里有一个高频报错:打包时配置了iOS的uni-push功能,但后端未配置对应推送证书,云打包虽然能出包,但推送在真机调试时永远不成功。笔试给的场景正是“云端服务器返回错误:当前应用打包时配置了ios uni-push功能,但uni-push未配置io级别推送证书”。我答的排查路径是:先在manifest里检查Push模块是否勾选,再去开发者后台确认APNs证书的Bundle ID是否匹配,最后看UniPush的AppID和AppSecret配置,排查顺序不能乱。
4.5 iOS混合开发方案选型
混合开发题更像一道路线规划题。题目问“如果现有App要嵌入一套H5支付页,同时要复用原生摄像头能力,选什么方案”。我答的是原生WebView + JSBridge + 系统相机API的组合,理由是这种需求强依赖原生能力,纯H5方案在iOS上调用相机涉及私有API风险,而纯Flutter/RN方案要在现有原生工程里引入整套引擎,体量太大。
这种题没有标准答案,但出题人希望看到你评估风险的能力。我提到了WKWebView的MessageHandler和cookie同步问题、H5端调用相机的权限申请流程,以及页面关闭时对JS端异步回调的引用释放。虽然答得比较零碎,但这些细节才是日常调研中会踩到的点。
5. 开放题与答题策略复盘:想清楚比写得多重要
5.1 测试回归题:iOS设备模拟的边界
笔试临近结束有一道关于测试策略的开放题:在只有Mac没有真机的情况下,怎么验证iOS 17.5新特性兼容性。我用的是Xcode的Simulator配合XCTest UI测试,优先跑最关键流程,同时用“通配符描述的模拟器”覆盖不同机型尺寸。但坦白讲,模拟器不能替代真机的相机、推送和蓝牙。我答题时明确写了:模拟器可以验证界面和数据流;蓝牙、推送、后台定位、证书信任等必须靠真机。
这题后面追问的是“为什么很多团队在iOS版本兼容性测试上翻车”。我提了一点:iOS系统版本与硬件绑定严重,老版本iPhone不能升级到最新系统,新机型又带了很多旧机型没有的特性,所以“低版本系统+新硬件”这种组合只有在用户手里才有。笔试题能考到这个层面,说明他们内部应该吃过这个亏。
5.2 我的答题策略与时间分配
整套笔试给的时长是120分钟,体量并不少。我做题的时间分配大致是:基础题45分钟,工程化题35分钟,开放题和检查30分钟,最后留10分钟补充之前没写完整的答案。遇到过一道题不太确定,我先写了关键词和方向,等后面做完再回头补细节。这种策略笔试很管用,不要在一道题上死磕,先把整张卷子的得分点摸到。
另外一点小心得:如果是线上笔试,建议把题目要求复制到本地笔记里,边答题边留痕。尤其是像“charles ios抓包”这种流程题,你的步骤顺序就是阅卷人判断是否真做过的重要依据。先写安装证书,再写设置信任,顺序反了虽然也能抓包,但阅卷人一眼就知道你没跑通过。
5.3 题目背后隐藏的能力坐标
整份试卷写完,我最大的感受是:这批题目不在于难,而在于全。它像一名iOS负责人的能力坐标轴,横轴是语言基础、系统框架、UI/交互、网络、存储、并发、性能;纵轴是开发、调试、打包、上架、运维、迭代。你只要在任何一个环节有真实项目的强化经验,答起来都会比死背书顺畅很多。
尤其那些工程化题,比如“iOS开发者App证书更新”“加急审核地址”“TestFlight测试包全流程”,如果只是看过博客没实操过,很容易卡在“下一步该点什么”上。我建议今年准备春招的iOS岗位同学,别只刷LeetCode和八股模板,把一整个外包项目从创建到上架的链路亲手走一遍,再回头做这套题,会轻松很多。
6. 踩过坑之后的几点备考建议
笔试结束后,我把自己答题卡上标注过“不确定”的点重新过了一遍,发现不少地方其实和我日常开发中的习惯有关。比如UIStackView分屏的坑,我虽然在项目里用过,但从没主动在Compact宽度下测试;再比如BLE连接参数,我只知道选30ms、0延迟、5s超时,却从没算过和连接间隔的倍数关系。备考与日常开发之间的差距,往往就体现在这里。
对正在准备iOS春招的同学,我建议优先补这四块:
- 内存管理与RunLoop原理:建议直接看objc4官方源码,配合《Objective-C高级编程》那本小册子吃透weak、autorelease、runloop observer。
- 系统状态机:蓝牙授权、相机权限、定位权限这类“系统级与App级状态”的差异题,多看Apple的About Privacy和WWDC Session。
- 完整上架流程:从申请证书到TestFlight外测,再到App Store审核,每一步动手过一遍,别只看教程。
- 跨端工程化:uniapp或Flutter打包流程要了解,现在很多岗位要求原生和跨端混合开发,知道云打包、离线打包、推送证书配置是怎么回事就够用。
笔试只是第一关,面试大概率会顺着卷子里某道题继续追问到底层原理。如果时间有限,最值得深挖的是weak的SideTable和RunLoop的observer回调时机,这两个点几乎被所有iOS面试官偏爱,我这次笔试个人感觉也是这两道题拿分最有底气。