ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

2023小满春招iOS笔试复盘:内存管理、Runtime与工程架构核心考点精讲

2023小满春招iOS笔试复盘:内存管理、Runtime与工程架构核心考点精讲 考完2023年度小满春招iOS研发岗第三批笔试说实话比我预想的要扎实不少。虽然挂着“笔试”的名头但题目不是那种背一背八股就能过的选择题更像是把生产环境里真正会遇到的坑、真正要做的设计决策搬到试卷上让你在有限时间内给出方案。整张卷子覆盖了Objective-C和Swift两套体系从内存管理、Runtime、网络层到工程架构、多任务适配都有涉及题量不算特别大但每一道题都值得认真对待。这篇文章我会把这次笔试的完整复盘写下来包括考点拆解、我当时答题的思路、踩过的坑还有考后和其他候选人交流整理出来的补充答案。不管你是准备春招的应届生还是打算跳槽的iOS开发这份复盘应该都能帮你少走不少弯路。1. 笔试整体结构与考核方向先说说这张卷子的整体布局。整场笔试一共120分钟题型分为三块基础客观题约30分、简答与场景设计约40分、在线编程题约30分。从分值分布上就能看出来这家公司更看重的是你能不能把知识点落到实际开发里而不是单纯背概念。客观题部分主要以选择题为主考的都是一些高频基础点。比如weak和assign的区别、copy和strong在什么场景下会出问题、Block的循环引用怎么破、UITableView卡顿可以从哪些方面优化、Crash日志里的堆栈怎么读、dyld加载流程里__attribute__((constructor))是什么时机执行的。这些题难度不大但有个特点选项里经常会出现两个看起来都对的说法需要你准确判断哪个是当前语境下最合适的。简答题和场景设计题就比较有意思了。题目不是问你“什么是Runtime”而是直接抛一个场景“线上有个页面偶尔Crash日志指向objc_msgSend怎么定位”或者“App启动时间从2秒降到1秒以内前后端和客户端各能做什么”这种题目没有标准答案考的是你解决问题的完整思路和工程经验。在线编程题有两道一道是链表相关的算法题后面我会详细说另一道是iOS相关的应用题实现一个图片内存缓存工具要求同时考虑内存占用、线程安全和淘汰策略。这两道题都能在LeetCode和实际工程里找到影子但第二道题更考察你对iOS内存机制的理解深度。整体来说这批笔试的定位很清楚筛掉只会用框架、不懂原理的人留下能独立解决线上问题的人。如果你平时开发时习惯多问一句“为什么”多去翻翻Apple文档和底层源码这套卷子对你来说会很舒服如果只是停留在写UI、调接口的阶段可能就会比较吃力。2. 笔试中的核心考点拆解2.1 内存管理ARC、循环引用与泄漏排查这一块基本上是iOS笔试的必考区小满这批题也一样但问法比大多数公司更细致。选择题里有个印象很深的题目__weak修饰的变量在ARC下访问时为什么需要加autorelease这题很多人会答错因为大家普遍只是“知道”weak变量会自动置nil却没想过访问weak变量时编译器做了什么。实际上编译器在读取weak变量时会把对象retain一下、加入autoreleasePool这样才能保证在表达式求值期间对象不会被释放。如果你只答“weak会自动置nil”只能说及格但答不出这个autorelease机制说明你对底层还不是真明白。简答题部分有一道循环引用的场景题大概是这样的NSTimer在ViewController里被addTarget的方式使用页面退出后Timer仍然在跑导致ViewController无法释放问怎么解决。我看到题之后先列了两层方案第一层在viewWillDisappear或dealloc里invalidate第二层换成block方式的Timer并weak捕获self。但题目进一步追问如果Timer是repeat模式继续用invalidate方案有什么风险这里其实是在考察你知不知道Timer在runloop里的强引用问题以及iOS 10之后引入的block初始化方式timerWithTimeInterval:repeats:block:为什么能天然避免循环引用。我在答案里额外补充了一点工程经验即使加了block版本的Timer也要在页面不可见时销毁或暂停否则后台挂起时Timer持续唤醒CPU既耗电又可能导致奇怪的Crash。这算是我做项目时踩过的坑写上去之后答案的“工程感”会明显不一样。2.2 Runtime与Runloop从消息发送到卡顿监控在我接触过的所有iOS题库里Runtime几乎是永远绕不开的大头。小满这张卷子没有直接问“Method Swizzling的原理是什么”而是绕了个弯“线上一个方法实现突然不生效可能是哪些原因造成的”这其实是把Runtime知识和Crash排查结合起来了。我当时拆了几个方向第一是否被其他组件Swizzle过导致IMP指向变化第二消息发送过程中是否有Category覆盖、继承链里是否出现同名方法第三是否存在respondsToSelector和forwardingTargetForSelector链路被截断的情况第四是否因为dyld加载顺序导致某个load方法还没执行。每个方向我都写了排查手段比如用class_getMethodImplementation直接看IMP地址、用method_getImplementation对比Swizzle前后是否一致。Runloop这块虽然没有单独出题但在场景题里出现过一次“线上卡顿如何监控”的变体。我的思路是通过监听kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting两个状态计算单次循环耗时如果超过阈值比如50ms就认为是卡顿然后把当前主线程的调用栈采集并上报。这里要精确到CFAbsoluteTimeGetCurrent()取差值也要注意在子线程里做监听避免影响主线程。2.3 网络层HTTPS、DNS与抓包工具实战网络相关的题目在这批笔试里占了大约15分涉及URLSession用法、HTTPS握手流程、DNS解析异常处理、以及Charles抓包配置思路。客观题里有一个针对App Transport Security的多选题问哪些配置能允许HTTP明文请求但我注意到干扰项里混了NSAllowsArbitraryLoadsInWebContent和NSAllowsLocalNetworking这两个容易混淆的key做的时候要仔细。简答题直接来了一道Charles抓包的配置题。题目给了三个现象装了Charles证书后App里的HTTPS请求仍然报SSLHandshakeError部分接口能抓到明文但图片域名抓不到浏览器能访问的页面在App内打不开。这题我的回答分了四个步骤正好可以展开讲讲。第一步电脑端安装Charles根证书打开Proxy - SSL Proxying Settings在SSL Proxying标签页勾选Enable并在Location里填入*:443这一步是允许Charles解密所有443端口流量。第二步手机端连上同一个WiFi把HTTP代理指向电脑IP和Charles默认的8888端口。第三步手机浏览器访问chls.pro/ssl下载并安装证书注意iOS 10之后需要在设置 - 通用 - 关于本机 - 证书信任设置里手动开启完全信任否则抓包时依然会报SSLHandshakeError。第四步如果还是抓不到检查App是否开启了证书校验比如SSL Pinning这时候就得通过逆向或拿到服务器端证书的方式绕过但一般笔试只要答到“用Charles动态替换证书”的程度就够了。这里有个特别容易踩的坑iOS 13以后如果App在Info.plist里设置了NSAllowsLocalNetworking为YES而你的代理指向的是局域网IPCharles可能抓不到流量这是因为系统对本地网络有额外的权限提示。我第一次配置时也卡了很久后来发现需要在弹窗里允许App访问本地网络不然手机上的请求根本不会走代理。笔试现场我不仅写了配置步骤还把这类权限问题也写进去答得比较完整。2.4 组件化与工程架构从CocoaPods到模块通信架构类的题目这几年在iOS笔试里出现频率越来越高小满这批也不例外。有一道简答题问的是“一个中型App要从单工程拆成多个模块你准备怎么拆模块之间怎么通信”我一开始看到这题有点意外因为这更像系统设计而非纯iOS题但仔细想想确实很有必要公司发展到一定规模后代码复用、编译速度、多人协作都是绕不开的治理问题。我的回答是先划分基础层和业务层基础层包括网络库、存储库、埋点库、UI组件库它们不依赖任何业务模块业务层再按领域拆比如登录、首页、订单、个人中心。模块之间不能直接import对方的头文件而是通过接口层Protocol和路由表机制来通信比如用MGJRouter或自己实现一个基于URL的中间件。同时要利用CocoaPods的development pod能力让每个业务模块作为一个独立的Pod工程既可以独立编译运行也可以被主工程依赖。这题还追问了“怎么保证拆完之后主工程编译时间不变慢”我的思路是使用二进制化方案把稳定模块编译成.framework静态库配合cocoapods-bin这类插件做源码和二进制之间的切换。另外还要统一依赖锁定版本避免两个模块引用同一个第三方库的不同版本导致duplicate symbol。笔试结束后我和一个同样投了这个岗位的同学聊过这题他的角度不太一样更偏重组件化后的“编译缓存”和“远程二进制仓库”我当时只想到本地二进制化后来觉得他的思路其实更贴合大规模团队的诉求只是实现成本更高。如果正在备考的同学建议把这两种方案都吃透。3. 代码题与系统设计题复盘3.1 手写链表逆序从标准解法到边界处理在线编程题第一道是“单链表逆序”。这题本身不新鲜但小满给了两个限制不能用递归空间复杂度O(1)。这说明他们很在意你是不是真的弄懂了指针操作而不是只会照抄递归解法。我的做法是经典的三指针法。定义prev为nilcurrent指向头节点每次循环把current.next暂存到nextTemp然后把current.next指向prev整体往右移动一格直到current为nil。核心代码大概长这样- (ListNode *)reverseList:(ListNode *)head { ListNode *prev nil; ListNode *current head; while (current ! nil) { ListNode *nextTemp current.next; current.next prev; prev current; current nextTemp; } return prev; }但笔试不只是让你写对还会看你有没有处理边界情况。我在写完主体后补充说明了几点空链表直接返回nil只有一个节点时循环体不执行prev就是这个节点如果调用方在逆序之后还需要引用旧的头节点要注意此时旧头节点已经变成新尾节点它的next已经变成nil继续遍历会导致提前终止。这道题还有个隐藏考点既然是链表操作那题目有没有要求就地反转还是允许新建链表如果允许新建链表用头插法就能轻松实现但笔试明确要求O(1)空间复杂度所以必须就地操作。我在提交前又检查了一遍是不是真的只是指针移动没有隐式地copy节点。总体来说这题只要基本功扎实都不会丢分关键是别在简单题目上大意。3.2 图片内存缓存工具多线程与淘汰策略第二道编程题是“实现一个图片内存缓存工具”要求支持以下功能存储和读取、按容量淘汰LRU、线程安全。看起来是个LRUCache经典题但加了iOS内存管理特性之后难度就上来了。我的核心思路是用NSMutableDictionary加双向链表实现LRU。字典负责O(1)查找双向链表负责记录访问顺序。每次get的时候如果命中就把对应节点移动到链表头部每次put的时候如果缓存满了就淘汰链表尾部的节点。为了保证线程安全所有操作都包在dispatch_queue_t串行队列里比直接加synchronized性能更好。当时我特意在答案里写了iOS内存压力的处理监听UIApplicationDidReceiveMemoryWarningNotification收到警告后清空缓存。另外UIImage的存储有个坑——直接存UIImage对象可能会因为image的解码数据占大量内存更好的做法是先拿到imageData或CGImage再解码成UIImage存储这样查询速度快但也没必要提前解码。在这一题上我采用了最大开销约几十MB、存储单位是UIImage的实现同时在注释里说明了如果图片很大可以改用NSCache加costLimit的方式核心是让系统在内存吃紧时自动清掉不常用的图片。这题还问了“为什么用串行队列而不是并发队列”我觉得这题是额外的加分问所以多写了一段解释并发队列在写操作时需要额外加锁在锁内做链表操作容易造成性能回退串行队列天然保证互斥且不丢操作读取时可以做一些优化比如用dispatch_sync快速返回或者改用pthread_rwlock做读写锁。3.3 App启动优化设计题从pre-main到页面首帧启动优化是每次面试高频题这批笔试把它放到了场景设计题里题目是“App启动时间从2秒降到1秒以内你会怎么做前提是不能砍功能。”我把它拆成两个阶段来答main()之前和main()之后。main()之前主要看dyld加载动态库、执行load方法、C全局对象构造这几步。我的方案包括减少动态库数量最好控制在5个以内把不需要在启动时加载的库改成懒加载检查所有load方法能移到initialize或首帧之后再执行的都移走减少Objective-C类数量和分类数量因为每个类都会增加启动时的objc_setClass耗时如果做了富文本或图标字体考虑用PingFangSC等系统字体替代。main()之后主要是首帧渲染路径上的工作。我的做法是先梳理冷启动阶段主线程执行的所有任务区分哪些是同步必须的、哪些可以异步。例如启动广告SDK初始化可以推迟到首帧后网络请求要尽早发起但不能阻塞首页数据如果来自缓存先渲染缓存再等网络刷新剪裁和压缩首屏图片避免首帧内加载超大图。当时我写的方案里还包含一个关键点使用Instruments的Time Profiler和App Launch模板打点测出启动阶段各环节耗时用数据驱动优化而不是拍脑袋改代码。这道题的分值特别高因为同时考察了系统原理、工程经验和性能工具链。我觉得平时如果有做过启动优化项目答起来会非常占优势。4. 高频工程问题与工具链经验4.1 证书更新与上架流程盘点笔试多选题里有道题考的是“iOS开发者证书更新后App出现无法安装或崩溃原因可能是什么”这题其实是在考察你对签名机制的理解。证书链没搞明白、描述文件过期、设备UDID未注册、Entitlements里多加了Capability但后台没开通都可能导致安装失败。还有一道场景题说的是“企业内部账号和开发者账号的证书有什么区别”这就要从Provisioning Profile、Team ID、App ID的角度去答。我的经验是日常开发中常备两个Profile一个是开发调试用的Development Profile一个是上传TestFlight或App Store的Distribution Profile。每次更新证书后第一件事是去Apple Developer后台确认过期时间第二件事是删掉钥匙串里旧的证书重新下载.cer并双击安装然后回到Xcode里Signing Capabilities页签重新选Team和Profile。如果出现“Your account already has a valid iOS Distribution Certificate”这种报错多半是因为本机钥匙串里已经存在同名证书需要把旧证书全部删干净再重新生成。在上架这块笔试还瞄了一眼加急审核的流程问我“App审核被拒后哪些情况可以申请加急审核”。我的答案概括为线上出现严重崩溃且影响用户使用的涉及支付或安全漏洞的版本无法正常打开或首页白屏的。加急审核要通过Apple官网“Contact Us”提交申请写清楚受影响的用户量、崩溃日志、复现步骤审核团队一般1到2小时会给回复。但要注意加急审核不是用来解决“正常功能被拒”的如果只是UI不规范或隐私文案问题老老实实修改重新提交更靠谱。4.2 分屏与多任务适配iOS 11以上的兼容策略这批笔试里我没有想到会考到分屏适配但确实有一道选择题问的是“iPad分屏模式下App应该怎么做布局适配才能避免元素被裁切”。这题的关卡明显是想考察对UIWindow尺寸变化和TraitCollection的理解。我的思路是不要用frame固定的方式写布局尽量使用Auto Layout通过viewWillTransitionToSize:withTransitionCoordinator:和traitCollectionDidChange:来感知尺寸变化对UISplitViewController做适当的折叠与展开处理关键的交互按钮不要放在屏幕最边缘因为分屏模式下边缘空间可能被系统手势占用。这道题还延伸到SceneDelegate在多窗口场景下的生命周期管理。我在答案里补充说iOS 13之后多窗口支持已经系统化开发者需要区分“单窗口App”和“多窗口App”。如果只是做兼容至少要在Info.plist里声明UIApplicationSupportsMultipleScenes为NO避免iPad上被系统当作多窗口App处理导致某些页面手势冲突。4.3 电池优化与后台任务处理电池优化在热词里出现了笔试里也真有几道与之相关的题——毕竟移动端App耗电太猛会被用户骂也会直接影响产品留存。小满的笔试题问的是“后台播放或定位时怎么减少耗电”。我答的是优先使用系统级API比如CLLocationManager的startMonitoringSignificantLocationChanges在重大位置变化时再唤醒而不是持续用startUpdatingLocation。后台任务用BGAppRefreshTask或BGProcessingTask来处理不能依赖beginBackgroundTaskWithExpirationHandler无限保活因为系统给的时间很有限。另外检查AVAudioSession有没有在不需要时被占住很多App因为后台音频会话没释放导致耗电明显。另外判断UIApplication的backgroundTimeRemaining也不可少我在线上项目里见过一个案例每次进后台都调用beginBackgroundTaskWithExpirationHandler但忘了在任务完成后调endBackgroundTask导致后台时间被大量占用系统频繁唤醒CPU电量掉得特别快。后来排查了很久才发现是这个原因笔试里遇到类似场景我一定会提这个反例。4.4 自动化测试与iOS开发者模式自动化这块客观题考的并不深基本都是选择题级别比如“XCTest里setUp和tearDown执行顺序”、“UI测试怎么等待异步元素出现”。比较有意思的是有一道题提到“iOS开发者模式”问的是“在什么场景下需要打开开发者模式”。我看题时愣了一下因为“开发者模式”更多出现在Android上但iOS上确实也有Xcode 14之后在真机调试和启动XCTest时系统会要求设备开启开发者模式。具体操作路径是设置 - 隐私与安全性 - 开发者模式 - 打开然后重启设备。如果不开Xcode会一直报“Could not launch because the device is locked”或者“Developer Mode is disabled”。这道题估计是吃过亏的工程师出的答出来能体现你的工程经验不是只看理论书能答出的。我还把XCTest里比较实用的做法写进了答题框用waitForExpectations替代固定sleep避免测试不稳定用XCUIApplication的launchArguments做测试数据注入避免依赖真实网络。5. 备考建议与我的实操心得整套卷子做完最强烈的感受是单纯背面试题已经不够了笔试越来越像一场“贴身肉搏”的工程诊断。很多题看上去是在问知识点实际上是在考察你有没有在真实项目中踩过坑、有没有看过日志里的栈、有没有用工具定位过问题。如果你现在还在准备阶段我建议你从下面几个方向去看。第一把Apple官方文档当“字典”而不是“教材”。比如Block的捕获列表、Runloop的几种模式、NSCache和NSDictionary的区别官方文档可能一两句话带过但面试官追问时往往就是这些细节。我的习惯是遇到一个API先点进去看Discussion部分再在工程里写个小Demo验证不确信用法就翻源码这种习惯能让你在笔试里多出很多“别人没写过但考官想看到的答案”。第二多看线上问题复盘。网上有很多Crash分析、性能优化的技术博客不必全部看完但要有体系地看。比如“启动优化”这块先看pre-main阶段有哪些耗点再看首屏渲染过程中有哪些优化空间最后看Instruments里怎么量化。这个过程比刷LeetCode还重要。第三把面试题当作Prompt去扩展。看到“iOS架构”这个热词别只知道MVC/MVVM要想到组件化、模块通信、二进制化、代码分层、依赖注入看到“iOS自动化”除了XCTest还应该知道XCUITest、fastlane、TestFlight自动分发看到“iOS分屏”要想到UISceneSession、TraitCollection、UIWindowScene。一个热词背后就是一套完整的知识树。我再分享一个小技巧。笔试时间有限遇到不会的题不要空着把你能想到的思路、排查步骤、可能用到的API名称都写上去。阅卷人看的不仅是答案还有你的思考路径。哪怕答案是错的只要路径合理、能自洽也能拿到大部分分数。反过来如果一道题只写了“不会”或者空着那基本就是零分。这次第三批笔试整体难度中等偏上但题目出得很实在。如果你能把上面这些内容消化掉再配合一两年的实际项目经验应该是能稳稳过关的。最后祝正在准备春招的小伙伴们都能拿到自己想要的Offer有面试题或笔试复盘方面的问题也欢迎随时交流。
返回列表