
京东2019春招iOS开发类试卷当年在求职圈里也算是话题度挺高的存在。到现在还有不少人在网上找这套题甚至有人把它当成iOS面试题库来刷。抛开“大厂真题”的光环从技术角度看这套试卷覆盖的知识点其实非常典型几乎把iOS开发必备的基础能力、系统机制、架构理解和实战经验都串了一遍。我当年整理过这套题的复盘笔记也用它带过几个实习生今天就把拆解过程和大家聊透。先说清楚这套试卷是京东2019年春招的iOS开发类笔试题主要面向校招和实习候选人考察范围包括Objective-C语言基础、内存管理、Runtime、RunLoop、多线程、网络、架构设计以及部分实战场景题。它的价值不在于“押题”而在于通过一套题把iOS开发者的知识体系完整地考察了一遍。适合准备大厂iOS面试的同学、带新人的iOS Leader以及想系统查漏补缺的开发者参考。1. 试卷整体观感与考点分布1.1 京东系笔试的风格定位京东的笔试向来和阿里、腾讯的风格不太一样。阿里的题更偏开放式设计和业务场景腾讯的题喜欢考细节实现京东的这套iOS试卷则更像一份“基础能力体检报告”——客观题占比高知识面覆盖广深度不算特别夸张但很考验基础是否扎实、经验是否成体系。整套试卷给人的感觉是不指望你做出什么惊天动地的设计但如果你连基础概念都答不清基本就被卡掉了。它考察的不仅是“知不知道”更是“有没有真正理解”。举个例子同样是问“为什么Block会循环引用”有人只答“因为self被block持有”而高分答案会从堆栈Block的复制、捕获变量的方式、打破循环的时机这几个维度展开。这种差异恰恰是京东这类大厂筛人的核心逻辑。1.2 五类高频考点的占比逻辑从我整理的版本来看这套试卷的考点大概可以分成五类语言特性Objective-C与Swift、内存管理、系统机制Runtime/RunLoop/多线程、网络与数据持久化、架构与实战场景。前三类大约占65%到70%后面两类占30%到35%。这个占比和一线iOS开发的日常工作是匹配的。日常开发中真正决定代码质量的上限往往不是你会用多少API而是你对语言特性的理解深度、对系统机制的把控能力。比如内存泄漏问题表面看是工具使用问题深层看是对引用计数的理解是否透彻。京东的这套试卷把重头放在基础和机制上其实也反映了他们对工程师基本功的要求。另外要提醒的是这套试卷出现了一些选做题和开放题这类题目才是真正的拉分项。客观题大家都能答个七八成开放题却能看出应聘者的思路、表达和技术视野。2. 核心考点逐题拆解语言与内存的硬功夫2.1 Runtime机制与消息传递Runtime在iOS面试中的出现频率高到已经快成“必考项”了。京东这套卷子也不例外而且考得比较细。核心问题围绕几个方向消息发送流程是怎样的、什么是消息转发、Method Swizzling的原理和风险、关联对象的实现方式。先说消息发送流程。Objective-C的方法调用编译后实际上会转换成objc_msgSend(id self, SEL op, ...)。这个函数的执行流程可以概括为先通过isa指针找到类对象再在类对象的方法缓存和方法列表中查找对应的IMP如果没找到就去父类继续查找直到NSObject为止如果始终找不到就会进入消息转发流程。这里有个很多新手容易忽略的细节方法查找过程中缓存只会在查找命中的时候写入不会在查找失败时写入负向缓存。所以频繁调用一个不存在的方法性能损耗会非常明显。我在实际开发中遇到过因为动态方法解析没实现导致某个API被高频调用时出现卡顿的情况后来用Instruments看调用栈才定位到是消息转发链路太长。消息转发又分三级动态方法解析resolveInstanceMethod:、快速转发forwardingTargetForSelector:、完整转发methodSignatureForSelector: 和 forwardInvocation:。这三级的调用时机和适用场景完全不同。动态方法解析适合给类动态添加方法快速转发适合把消息转给其他对象完整转发可以自定义参数和返回值但成本最高。Method Swizzling也是高频考点。这套题更关注的是Swizzling为什么要在load方法里做、为什么需要dispatch_once、以及它和KVO的冲突问题。我踩过一次坑对一个系统方法做Swizzling时没有用dispatch_once导致在多个线程同时触发时交换了两次IMP结果方法实现彻底错乱回调也没了。所以Swizzling的写法必须严格规范哪怕是demo也要认真写。2.2 内存管理与循环引用内存管理这部分京东的试卷没有直接考“ARC和MRC的区别”这种送分题而是用场景题来考。比较典型的有两类一类是Block导致循环引用的场景判断另一类是NSTimer的循环引用问题。Block的循环引用考的是捕获变量对引用计数的影响。Block在栈上时不会持有捕获的对象但一旦Block被复制到堆上就会对捕获的对象执行 retain。所以在block内部使用self如果self又强引用了这个block就会形成 retain cycle。解决方案是使用__weak修饰的弱引用在block内部再通过__strong修饰的局部变量来保证执行期间对象不被释放。这里有一个“知其然更要知其所以然”的点为什么进block先转成__strong因为单纯的__weak引用在block执行过程中对象可能被提前释放导致后续访问到野指针。转成__strong之后block执行期间持有了这个对象执行完毕再释放这样既避免了循环引用又保证了执行安全性。NSTimer的循环引用问题更隐蔽。很多人在dealloc里做invalidate但NSTimer被加入RunLoop后会被RunLoop强持有而target又强持有self这条引用链很难通过dealloc来断开因为dealloc根本不会被执行。正确做法是在合适时机手动invalidate或者在iOS 10以后使用block版本的timer并配合__weak self。这套题如果要求写代码最好把block版本和iOS 10以下的兼容方案都写好。内存管理的另一个重要考点是weak的实现原理。weak引用为什么在对象释放后会自动置为nil因为Runtime维护了一个全局的weak表以对象地址为keyvalue是所有指向该对象的weak指针地址数组。对象释放时Runtime会根据weak表把这些指针依次置为nil。这个机制也解释了为什么weak指针不能用于非对象类型以及为什么weak变量的读取需要加锁。2.3 Block底层原理与变量捕获Block在iOS开发中的重要性怎么强调都不过分。这套试卷对Block的考察主要集中在三个层面Block的三种类型、变量捕获规则、__block的作用原理。Block的三种类型分别是全局BlockNSGlobalBlock、栈BlockNSStackBlock、堆BlockNSMallocBlock。不捕获外部变量的Block是全局Block存储在全局区捕获了外部变量且没有复制操作的Block是栈Block栈Block调用copy之后变成堆Block。ARC下编译器会自动对Block做copy操作所以开发者直接接触堆Block的频率更高。变量捕获规则也是必须理清的。局部变量被Block捕获时是值拷贝所以Block内部修改局部变量不会影响原变量静态局部变量和全局变量则是引用捕获Block可以直接修改它们。这个知识点看似简单实际上很多面试者会混淆“值拷贝”和“引用捕获”的边界。__block修饰符的原理这道题几乎必考。__block变量在被Block捕获时会被包装成一个结构体结构体内部持有这个变量。这样Block修改的其实是结构体里的变量从而达到了修改外部变量的效果。在ARC下__block变量会被Block持有所以也需要注意循环引用问题。这个细节很少人注意到我在实际写代码时踩过一次后来就特别谨慎。3. 系统机制与并发面试官真正想考察的深度3.1 RunLoop的内部结构与运行逻辑RunLoop这套题京东考得很有代表性。核心问题包括RunLoop和线程的关系、Source/Timer/Observer三种模式的优先级和触发时机、RunLoop的退出条件。RunLoop本质上是一个事件循环它的存在是为了让线程在有事可做时保持忙碌、无事可做时进入休眠从而节省CPU资源、延长电池寿命。每个线程有且仅有一个RunLoop主线程的RunLoop默认启动子线程的RunLoop需要手动获取并运行。RunLoop的两种常见模式是NSDefaultRunLoopMode和UITrackingRunLoopMode。默认模式下 Timer 会正常工作但用户在拖动滚动视图时RunLoop会切换到 Tracking 模式默认模式的 Timer 会被暂停。这个机制解释了为什么在UIScrollView滚动时NSTimer会“卡顿”。解决办法是把Timer添加到NSRunLoopCommonModes下或者使用DispatchSourceTimer。RunLoop的Observer机制经常出现在性能优化的场景。比如用CFRunLoopObserver监听kCFRunLoopBeforeWaiting和kCFRunLoopAfterWaiting可以监测主线程卡顿情况这也是很多第三方卡顿监控框架的底层原理。如果你在笔试中能把这个场景写出来会让面试官觉得你不仅懂概念还知道怎么用。这套卷子里还出现过一道和RunLoop相关的实战题如何实现一个常驻线程。答案是用NSThread创建一个子线程在子线程里获取RunLoop并添加一个Port源然后启动RunLoop。常驻线程在AFNetworking的旧版本、SDWebImage等框架中都有应用至今仍然是处理某些后台任务的有效手段。3.2 多线程方案对比与死锁分析多线程在iOS中的实现方案有四种pthread、NSThread、GCD、NSOperationQueue。这套题的重点放在GCD和NSOperationQueue的对比上。GCD更适合处理简单的并发任务语法简洁、性能出色NSOperationQueue基于GCD封装但支持任务依赖、取消、最大并发数控制更适合复杂业务。死锁问题是GCD题目中的经典陷阱。我在实际辅导中经常看到有人答错这道题在主线程执行同步任务会死锁吗答案是会。因为主线程在执行同步任务时会等待任务在同一个线程上执行完毕但任务又被添加到同一个串行队列的末尾形成了相互等待线程直接被卡死。这是面试中最常见的现象。那如何避免死锁呢关键是理解队列和线程的关系。同步任务不会开启新线程而是在当前线程执行异步任务会开启新线程主队列除外。所以如果你不确定当前代码运行在哪个队列尽量避免使用同步任务。实际开发中我习惯用dispatch_async往主队列派发UI更新用dispatch_sync到串行队列做数据保护时也会仔细确认当前线程不是该串行队列的线程。GCD的信号量也是一个值得展开的考点。dispatch_semaphore_create(0)配合dispatch_semaphore_wait可以把异步任务变成同步等待适合控制并发数、等待多个异步任务完成。但信号量很容易造成死锁因为wait会阻塞当前线程如果等待的条件永远不满足线程就永远卡住。真题中如果出现“如何等待多个网络请求全部完成后刷新UI”可以用DispatchGroup也可以用信号量但DispatchGroup显然是更安全的做法。3.3 APNs推送的完整链路APNs推送在2019年的这套考卷中出现有点意外但也符合京东这样的大厂对移动端工程师的能力要求——推送是电商App的核心业务能力之一订单状态、物流信息、促销活动都依赖推送触达用户。APNs推送的完整链路是应用向系统申请推送权限系统返回deviceToken应用把deviceToken上传到自己的服务器服务器构造推送消息调用APNs接口APNs找到对应的设备把消息下发到系统的通知中心用户点击通知后系统唤起App并传递推送内容。这套体系有两个容易考到的细节。第一个是deviceToken的获取时机。iOS 13以后系统会在App启动后通过delegate方法回调deviceToken开发者需要在这个回调里把token上传到服务器。第二个是前台推送的处理。App在前台时系统默认不展示通知横幅需要实现willPresentNotification方法用completionHandler来决定是否展示、如何展示。关于推送的离线消息处理还延伸出一个问题用户点击推送时App还没启动怎么拿到推送内容答案是launchOptions字典。这个知识点在笔试中如果考到最好把冷启动和热启动两种情况都答出来会显得更有经验。4. 架构设计与实战场景笔试中的拉分题4.1 iOS架构模式从MVC到MVVM再到组件化京东这套试卷的开放题部分几乎一定会出现架构设计。常见问法是谈谈你对MVC、MVVM和组件化的理解以及你在项目中的实践。MVC是iOS开发最基础的架构模式C作为中间层负责协调M和V。但标准的MVC在iOS中很容易变成 Massive ViewController因为ViewController既是控制器又是视图容器业务逻辑很容易堆积进来。MVVM在MVC的基础上引入ViewModel把视图展示逻辑从Controller里抽离配合RAC或Combine可以显著降低Controller的复杂度。但MVVM不是银弹。我在项目里遇到过ViewModel过度设计的情况一个列表页的ViewModel写了两千行数据绑定逻辑复杂到改一个需求要动五六个方法。所以架构的核心不是追新而是让变更成本可控。组件化是大型App架构演进的主题。京东这类体量的App客户端团队人数多、业务线多必须通过组件化来保障并行开发和独立发版。组件化的核心包括路由设计、组件通信、依赖管理、隔离规范。路由方案比较成熟的有URL路由和Target-Action两种。URL路由对H5和客户端统一但参数传递需要序列化Target-Action方案通过运行时转发调用关系更直接但要求组件间协议统一。笔试如果问“如何设计一个组件化方案”建议从业务划分、基础组件层、中间件层、App壳层四个维度来答同时给出组件间通信的约定和容错策略。4.2 网络层整体设计与数据缓存策略网络层也是大厂笔试的高频场景题。这套考卷出现过如何设计一个网络层要注意哪些问题一个比较完整的方案是底层基于NSURLSession封装统一的上层接口请求参数统一处理签名、加密、公共参数响应数据统一解析JSON转Model提供统一的错误处理机制网络错误、业务错误、服务器错误分开处理支持请求取消和优先级控制。这里有一个关键细节网络层是否需要做分页缓存和磁盘缓存。电商类App对数据完整性和实时性要求高一般只做内存缓存和短周期磁盘缓存新闻类或内容型App则可以做更长时间的缓存实现“弱网可读”。我在实际项目中常用的是“网络请求成功 - 更新缓存网络请求失败 - 读取缓存”这样的策略可以让用户在弱网环境下也有内容可用不至于白屏。缓存策略还需要注意数据一致性。磁盘缓存建议用文件或数据库存储并增加版本号和过期时间。一旦接口返回格式变更缓存也要同步失效否则容易出现旧数据展示异常。曾经有一个版本后端字段从price改成finalPrice但我们的缓存没有做迁移导致一批老用户看到的还是旧价格最后紧急发版修复。所以缓存设计必须包含版本管理机制。4.3 性能优化与包体积治理的经验性能优化属于实战能力考察。京东这套试卷在这块虽然题量不多但有一道让我印象深刻的场景题你在开发中遇到过哪些性能问题怎么定位和解决的回答这类题最好不要泛泛而谈“优化启动速度”“减少卡顿”要拿出具体案例。比如启动优化可以从pre-main阶段开始拆解动态库加载、类注册、分类方法注册、静态初始化每一步都有优化空间。实际项目中我做过将部分非核心动态库改为静态库启动时间减少了大约300毫秒。合理优化App启动时间可以让用户更早看到首页内容提升留存。页面卡顿的定位手段可以讲Instruments的Core Animation工具。通过查看FPS和图层混合情况定位到离屏渲染和图层混合是常见卡顿原因。修复方式包括给图片设置无圆角模式、优化阴影效果、减少图层层级。这里有个实操技巧用Instruments的Time Profiler查看调用耗时快速定位CPU占用过高的函数。包体积治理也值得准备一套方法。二进制瘦身的通用流程是分析资源文件占比、找出未使用的资源、对图片进行压缩和WebP转换、检查重复代码和重复二进制、合理配置bitcode。每减少1MB包体对转化率和下载转化都有积极影响一线大厂还在用“无用代码扫描”工具辅助排查。5. 面试延伸从笔试到一线的沟通5.1 如何把真题答案转化成面试表达笔试写的答案是知识储备面试说出来的答案才是能力展示。很多候选人在面试时遇到类似问题时容易把答案背得很干面试官追问两句就露怯了。比如提到“使用NSTimer需要考虑循环引用”面试官可能会追问“自动释放池和RunLoop的关系是什么”如果只是背了结论就接不住。我建议的准备方式每个核心知识点都准备一个“为什么”和“我遇到过的一个坑”作为例子。比如讲Block的循环引用可以顺手讲一次因为NSTimer导致页面无法释放的问题以及你是怎么通过Instruments确认并修复的。这样面试官听到的不只是答案而是你的真实经验和问题解决能力。表达能力同样重要。面试沟通中观点要直接、清晰不要绕弯子。回答问题前可以先花10秒组织一下逻辑分“结论-原因-案例”来展开面试官更容易跟得上。5.2 关于iOS自动化、上架与证书的必要背景虽然这套试卷没有直接考iOS上架流程但作为iOS开发者的职业背景证书、上架和自动化分发属于必备常识。因为很多大厂面试聊到项目经历时会顺带问一句“你负责过App的上架流程吗遇到过什么问题”证书管理的本质是信任链开发者证书用来证明开发者身份描述文件用来声明App的权限和设备范围推送证书用来建立APNs连接。开发环境和生产环境的证书是分开的需要分别配置。很多新手第一次配置推送证书时容易把证书导出成p12时选错密钥导致推送功能在测试环境正常、生产环境收不到这个问题排查起来很费时间。自动化打包和分发是提升团队效率的有效手段。现在主流方案是使用Fastlane配合Jenkins或GitLab CI实现提测包的自动构建和分发。iOS工程的签名配置建议使用Xcode自动管理但在组件化或多环境测试、灰度、生产的场景下建议使用xcconfig区分打包配置避免手动改Bundle Identifier导致签名错乱。5.3 给准备大厂面试的iOS工程师的复习清单结合这套试卷和这些年带人的经验我整理了一份复习优先级给正在准备大厂iOS面试的朋友参考。按投入产出比排序第一优先级内存管理、RunLoop、多线程、Runtime。这四项几乎是所有大厂笔试和一面必问的内容必须做到不仅能答概念还能写代码、能说清原理。第二优先级网络层设计、数据持久化、架构模式。这些是场景题的常客考验设计能力。第三优先级性能优化、组件化、自动化打包这类问题需要结合项目经历来回答如果没有实战经验可以从学习开源框架和参与开源项目入手补足。工具方面建议熟练掌握CocoaPods、Xcode Instruments、Charles抓包工具和LLDB调试。其中Instruments至少会用Leaks、Time Profiler、Core Animation这三个模板能让你在面对调优类问题时给出更有说服力的回答。6. 最后的经验之谈整理这套京东2019春招iOS开发类试卷的过程中我最大的感受是iOS开发这个岗位考察的从来不是你会不会写UI而是你踩过多少坑、有没有把坑背后的原理搞清楚。那些看起来很高深的技术面试题背后的底层逻辑其实很朴素——内存、并发、消息传递、数据流管理。只要把这些基础吃透面试时再遇到没见过的题也能举一反三答出个七八成。另外想多说一句别只刷题一定要动手写。哪怕是写一个简单的网络库、一个常驻线程的Demo、一个Block循环引用的复现场景都比背十道题有价值。我在做技术评审时最容易判断一个人是否真的理解某个知识点的方式就是让他现场写一段代码或者画一个类图。纸上得来终觉浅这个道理在iOS开发里尤其成立。