京东2019校招笔试ios开发工程师笔试题,在当年的iOS圈子里讨论度不算低。原因倒不是题目有多偏多怪,而是它的考察范围非常"典型大厂":既有语言底层原理,又有实际业务场景设计,算法题也占了不少比重。我当年完整参加了这场笔试,后来也帮学弟学妹复盘过这套题的出题思路,今天趁这个机会把整个考察逻辑和核心知识点拆开揉碎聊一聊,想投电商类大厂iOS岗的同学可以参考。
先说结论:这类笔试并不是在考"你会不会写Swift",而是在考"你作为一个iOS开发,懂不懂系统底层、能不能处理复杂业务、有没有基本的算法功底"。下面我按当年的题目类型和考察侧重点逐一展开。
1. 京东iOS校招笔试的考察逻辑与整体印象
1.1 试卷结构并不复杂,但覆盖面非常广
2019年的京东校招iOS笔试试卷,整体分为几个固定模块:OC语言与Runtime底层、内存管理、多线程、网络与数据持久化、UI与性能优化,再加上一部分算法和逻辑题。有个很直观的感受:题目几乎不涉及Swift,全部以Objective-C为主。这跟2019年京东主App的开发语言底色完全一致,即使当年Swift已经出了好几个大版本,但电商核心业务线为了稳定,依然大量使用OC维护。笔试考什么,基本映射了业务线正在用什么。
从难度梯度来看,前30%属于送分题,考察的是"是否真的写过iOS代码",比如strong和weak的区别、GCD和NSOperation的区别这类;中间40%开始区分熟练度,需要你理解消息转发流程、自动释放池的底层结构、运行时方法替换的坑;最后30%是拉开差距的题,往往结合JD的电商业务场景,比如海量商品列表的滑动优化、复杂网络请求的架构设计,还要手写算法。整体来看,这套题的目标不是筛掉"不会的人",而是直接定位"能干活且懂原理的人"。
1.2 电商大厂笔试的特点:业务场景和技术深度挂钩
很多同学刷了几个月LeetCode,觉得算法题刷明白了笔试就稳了,但京东这种电商大厂的iOS笔试有个明显的倾向:数据结构和算法的题目本身不难,难的是嵌在业务场景里。比如"实现一个商品SKU组合的快速匹配逻辑""设计一个订单列表的分页加载缓存策略"这类题,核心还是数据结构,但你需要同时考虑OC的语言特性和iOS的UI刷新机制。
我当时做笔试时有个很深的体会:大量题目都喜欢用"App启动流程""首页Feed流加载""购物车数据同步"这些电商App特有的场景去包装一个普遍的原理问题。所以备考时如果只看纯理论,不动手模拟业务场景,很容易在二选一的选择题里纠结。这背后其实反映了京东iOS团队的用人标准:不仅希望你来就能上手写业务,更希望你具备从问题场景反推技术方案的能力。
下面我按照笔试题目里出现频率最高的几个方向,逐个拆解当年到底考了什么、底层原理是什么、以及我踩过的坑。
2. OC语言与Runtime:京东笔试对运行时机制的偏爱
2.1 对象、类对象与元类的关系,不是背会isa就完了
京东笔试的单选题里,关于Runtime的第一类高频题就是对象、类、元类之间的关系。很多人以为这题考察的是"实例对象的isa指向类对象,类对象的isa指向元类"这张图,但实际的坑点往往藏在细节里:元类的isa指向谁?根元类的superclass指向什么?class方法返回的是类对象还是元类?
我当时在这类题上栽过一次,因为只看结论没推演。笔试里变着法问:对一个实例对象调用class方法,和对类对象调用class方法,返回结果分别是什么?前者返回类对象,后者返回自身。但如果你想当然地以为"class方法走的是元类的消息查找流程",就容易忽略NSObject对这个方法的特殊处理。这种题考察的不是记忆,而是你推导消息查找路径的能力。
2.2 消息转发机制:这几乎是必考的大题
有一道分值很高的简答题,让描述OC的消息转发过程,并要求说明每种转发阶段适合做什么业务。完整的消息转发分三个层次:
- 动态方法解析:调用
resolveInstanceMethod:或resolveClassMethod:,允许你动态给类添加方法实现。 - 快速转发路径:
forwardingTargetForSelector:,把消息直接转发给另一个对象处理,这也是很多代理模式、多继承模拟方案的底层基础。 - 完整转发路径:
methodSignatureForSelector:配合forwardInvocation:,先把消息打包成NSInvocation对象,再决定由谁处理、如何处理。
笔试答题的时候,只写三段式是不够的,要结合实际场景说明。我当时补充了JSPatch这类热修复方案利用消息转发做方法替换的思路,以及在组件化架构里用消息转发实现远端路由降级的例子。
2.3 KVO和KVC的底层原理,附带高频衍生题
KVO底层是通过动态生成子类并重写setter方法实现的,_NSSetBoolValueAndNotify这类方法会被动态插入,并在setter里调用willChangeValueForKey:和didChangeValueForKey:。这道题本身不算超纲,但京东笔试会有个进阶问法:手动触发KVO怎么做?什么时候KVO会失效?
最容易答错的是"KVO为什么在直接修改成员变量时不会触发"。因为直接走_ivar赋值,根本不经过生成的子类setter方法,通知机制自然不会被激活。我建议备考时实际写个Demo验证一下,比单纯背结论扎实得多。KVC的考察则集中在valueForKey:的查找顺序上,先找getter方法,再找实例变量,最后才走accessInstanceVariablesDirectly的兜底逻辑,很多人把顺序记反了。
2.4 Category和Extension、load和initialize:基础但极其容易混淆
京东笔试对Category的考察几乎每年都有。除了"Category能否添加成员变量"这个老生常谈的问题,真正的分水岭是:Category添加的方法和类本身的方法,谁在先谁在后?为什么多个Category的方法调用顺序不稳定?根源在于Category的方法并不会替换掉类里的原始方法,而是被拼接到了方法列表的前面。多个Category编译顺序不确定,方法列表中它们的顺序也就无法稳定,所以"最后一个编译的Category方法会生效"这种说法其实不严谨。
load和initialize也是高频选择。load是在类被加载进运行时环境时调用,不依赖调用方,即使没有任何代码引用该类也会执行;initialize则是首次向该类发送消息时才触发。注意子类和父类之间的调用顺序问题,笔试里经常拿"子类没有实现initialize时,调用子类会触发父类的initialize吗"这种场景来区分是否真正理解。
3. 内存管理与多线程:看过源码和没看源码,答题深度完全是两个级别
3.1 ARC下还有没有"内存管理题"?答案是不仅有,还很难
很多备战校招的同学以为ARC普及之后,retain和release不用手动写了,内存管理题就该消失了。事实恰恰相反,京东笔试在内存管理上的考察比很多公司都深,只不过重点从"手动管理引用计数"转移到了"理解引用计数的底层机制"。
比如weak弱引用,为什么对象销毁后weak指针会自动置nil?因为Runtime在对象销毁时会遍历SideTable中的weak表,把所有指向该对象的weak指针全部清零。这个机制本身不难,但笔试会考逆向场景:如果有一个__weak修饰的局部变量,它的引用计数会不会因为赋值操作而增加?答案是会,因为编译器底层为了保证弱引用的安全性,会临时retain一次,作用域结束后再释放,这就是为什么很多人以为weak不会改变引用计数,但用_objc_retainAutoreleasedReturnValue之类的工具能看到计数变化的原因。
3.2 autoreleasepool的边界条件,答错的基本都没看过源码
autoreleasepool的考察点集中在:它和RunLoop的关系是什么?主线程RunLoop在什么时机自动释放池?子线程是否有自动释放池兜底?
我在一本面试题集里看到过"autorelease对象什么时候释放"的标准答案是"当前RunLoop循环结束时",这个说法太粗略了。严格来说,是在当前autoreleasepool作用域结束时释放。主线程的RunLoop在每次事件处理前后会自动创建和销毁autoreleasepool,所以表现上像是"一次循环结束就释放"。但如果你在子线程里手动创建了一个autoreleasepool,作用域结束点完全由代码结构决定。笔试里用一个"for循环内大量创建临时字符串,内存峰值为什么容易暴涨"的场景来考,你需要答出:如果不加局部autoreleasepool,临时对象会被最外层的池统一管理,直到外层作用域结束才释放,而手动加局部池可以及时释放中间变量。
3.3 GCD死锁的经典场景,以及信号量在面试代码题里的用法
多线程部分,京东笔试题里GCD的比重远高于NSThread和NSOperation。最经典的必考题就是死锁:
dispatch_queue_t queue = dispatch_queue_create("com.jd.test", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ dispatch_sync(queue, ^{ NSLog(@"deadlock"); }); });这段代码必然死锁:外层block提交到串行队列,在等待执行时,内层又通过dispatch_sync往同一个串行队列提交任务,这个新任务必须等外层block执行完才能开始,而外层block又必须等内层sync返回才能结束,互相等待。笔试如果只要求判断是否死锁,很多同学能答对,但如果接着问"把串行队列换成并发队列会怎样""把外层async换成sync会怎样",就有人开始乱了。并发队列下dispatch_sync往自己内部提交任务不会死锁,因为不需要等前面的block完成就能并发执行;但外层也是sync时,依然会死锁,因为当前线程也在等待。
信号量dispatch_semaphore_t在笔试里更多是跟异步转同步结合着考:dispatch_semaphore_wait会让当前线程阻塞,直到信号量值大于0。这里有个细节,信号量超时等待的时间单位是dispatch_time_t,用dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC))表示3秒,很多人背了api但不会换算,笔试里容易栽在传参类型上。
3.4 多线程安全:atomic真的线程安全吗?这道送分题其实是个坑
atomic不等于线程安全,这已经是iOS面试的常识了,但京东笔试的变态之处在于,它会继续追问:"对一个atomic修饰的数组属性执行addObject操作,是否线程安全?"答案是否定的。atomic只保证了getter/setter的原子性,你拿到数组对象之后对这个可变数组做修改,不在atomic的保护范围内。同理,nonatomic的拷贝属性为什么适合NSString,因为copy修饰符涉及的是setter内部对传入值的拷贝策略,跟多线程无关。
面试题发展到今天,已经很少有人还停留在"atomic就是绝对安全"的认知上,但如果真问到"如何设计一个线程安全的数组容器",很多人会卡住。我当时答题的思路是:别一上来就加锁,而是先分析读写比和实际使用场景。如果读多写少,用读写锁或者dispatch_barrier_async配合并发队列;如果写频繁且单写多读,用pthread_rwlock;如果追求简单直接,用@synchronized但要注意性能损耗。笔试不要求你写完整代码,但需要你能对比不同锁的适用场景。
4. 网络层与数据持久化:电商App的链路稳定,靠的就是这些基础知识
4.1 HTTP/HTTPS的握手细节,京东笔试的思路是"从一次购物车同步说起"
电商App里最常见的网络请求场景就是:提交订单、同步购物车、刷新首页Feed流。京东笔试特别喜欢拿这些业务场景包装网络题。第一类高频题是HTTPS的握手流程,要求写出TLS握手的完整步骤,并说明客户端如何校验证书。
我建议答题时不要只写"客户端用CA公钥验证服务端证书",要展开:客户端收到证书后,先检查证书链是否可信,再检查域名是否匹配,最后用证书里的公钥协商出一个对称加密密钥。对iOS开发而言,还需要补充AFNetworking/NSURLSession默认的证书校验策略,以及如果碰到自签名证书或SSL Pinning应该怎么做。当年笔试考了一道App抓包相关的问题,大意是为什么配置代理后HTTPS请求能看到明文数据,这其实牵涉到中间人攻击的原理:客户端如果信任了代理服务器的根证书,就会把代理当作合法服务端,代理再和服务端建立真正的TLS连接,从而解密并转发数据。
4.2 DNS解析的坑:电商大厂必问的本地DNS与HTTPDNS
京东这种超大流量App,DNS问题几乎是所有网络层的核心痛点。笔试里问DNS解析的流程,如果你只写"从本地DNS缓存查到权威服务器"就太浅了。我当时在答题里补充了运营商DNS劫持和LocalDNS解析失败的问题,以及HTTPDNS通过ip直连解决传统DNS缺陷的思路。电商App的域名经常有多个CDN节点,不同地区用户解析到的IP可能不同,如果走了失效的IP,初始化网络层时需要自动降级和重试机制。这个知识点在笔试里可能只占一道选择题,但如果你准备面试,几乎必考。
4.3 数据持久化选型:从NSUserDefaults到SQLite再到Core Data
iOS的数据持久化方案,笔试常以"不同场景应该选哪种方案"的形式出现。京东的购物车、浏览历史、缓存商品列表这些功能,对持久化需求完全不同,题目会让判断:
- 存储用户偏好设置、登录token:首选NSUserDefaults,支持轻量级读写,但注意不要让大数据量走这个通道。
- 存储结构化数据且需要复杂查询:SQLite或用FMDB封装,适合商品列表、订单历史这类大规模数据。
- 存储对象图且层级复杂:Core Data更适合,但学习和调试成本高。
关于SQLite的一个高频细节题:为什么要用预处理语句(prepared statement)?答案是防止SQL注入、提高重复执行效率、避免重复解析SQL语句。数据库升级时的表结构迁移也是一个考点,笔试可能会给一个V1到V2的字段变更场景,问你怎么写migration逻辑,这对应了FMDB升级方案里的user_version判断。
4.4 大文件下载与断点续传:电商App更新包下载的隐藏考点
京东商城App体积不小,应用内更新和资源包下载是真实存在的需求。笔试里关于断点续传的题目通常这样出:客户端下载一个100MB的更新包,下载到60%时网络中断,如何实现续传而不重新下载?核心是HTTP头部的Range字段,客户端记录已下载的字节数,再向服务端请求剩余字节。服务端返回206 Partial Content,同时带Content-Range标识当前返回的字节范围。这个过程中需要处理本地已有数据的拼接和校验,如果服务器不支持断点续传,要能自动降级为全量下载。
5. UI与性能优化:京东这种数据量极大的App,如何保证FPS稳定
5.1 Auto Layout和Frame布局的取舍,笔试里换了个说法
京东笔试关于UI的题目很少直接问"Autolayout和Frame哪个好",而是会给一个具体场景:一个商品卡片需要根据图片高度动态变化,同时要适配不同屏幕尺寸,你会选择哪种布局方式?
Auto Layout的优势在于适配性强,但它的约束计算是有成本的,尤其是在大量Cell复用的情况下,如果约束非常复杂,CPU计算约束的时间会直线上升。Frame布局的优势是性能可控且计算直接,但要自己处理屏幕适配和旋转问题。笔试的正确答案通常不是二选一,而是"混合使用"。常规Cell部分用Auto Layout保证快速开发,对于超大列表中的复杂Cell,可以考虑预计算Frame缓存到内存模型里。我当时有段心得:在UITableViewCell的prepareForReuse里清理约束,有时候比优化约束本身更有效。
5.2 UITableView优化是京东笔试绝对的重点,没有之一
首页Feed流也好、商品列表也好,电商App的核心交互都集中在UITableView或UICollectionView上。笔试里性能优化那道大题,我几乎能猜到标准答案的几个方向,但能不能说得完整就看积累:
- Cell复用机制,注册重用标识符,避免每次创建新Cell。
- 避免Cell布局的重复计算,提前计算好行高并缓存。尤其是iOS 11之后Self-Sizing Cells虽然方便,但高度计算放在主线程的话,上下滑动依然会有卡顿。
- 异步绘制:把文本的绘制、图片的decode从主线程挪到子线程,只把绘制结果渲染到Layer上。
- 减少离屏渲染:尽量避免圆角+阴影+maskToBounds组合,如果必须使用圆角,考虑用贝塞尔曲线裁剪或者直接让UI切图。
- 图片加载与缓存:使用SDWebImage或YYKit,注意图片的尺寸要与显示尺寸匹配,不要加载一张大图直接丢给UIImageView,先做Downsampling。
这些点都比较常规,但京东笔试会加一道"如果列表卡顿,请你给出从主线程工具入手排查的具体步骤"的题。你需要回答通过Instruments的Time Profiler找到卡顿函数,查看主线程的调用栈,观察是否有耗时的文件IO、解压、布局计算,再进行针对性优化。
5.3 离屏渲染的本质:从GPU和CALayer的角度理解
离屏渲染这个概念在笔试里反复出现。很多同学能背出"设置圆角、阴影、maskToBounds会触发离屏渲染",但解释不了为什么。其实离屏渲染的本质是图层无法直接在当前渲染缓冲区完成合成,需要额外创建离屏渲染缓冲区和上下文,先渲染到离屏缓冲区再拿到结果,这个过程有大量GPU上下文切换开销。
我建议答题时从CALayer的绘制流程说起:CALayer有自己的contents和backgroundColor属性,正常情况下GPU可以把layer直接合成到屏幕上的帧缓冲区。但当你给layer设置了mask、圆角、阴影等效果时,GPU需要先渲染出一个中间结果,再一次合成到屏幕,这个过程就需要离屏缓冲区。频繁的离屏渲染会直接影响FPS。如果笔试里有优化题,你可以提到用shouldRasterize来缓存已经绘制好的layer,但要注意它也不是万能的,如果layer频繁变化,反而会浪费GPU内存。
5.4 启动时间优化:电商App启动快慢直接影响GMV
App启动优化的题目,在2019年后的校招笔试里频率越来越高。京东的App启动涉及的场景包括:读取本地缓存、初始化网络SDK、注册路由、配置日志系统、恢复登录状态。启动优化的两个关键阶段是main函数之前和main函数之后。
main函数之前的优化方向比较有限:减少动态库加载、合并静态库、减少+load方法、削减二进制体积。main函数之后的优化方向更多:将非首屏需要的SDK延迟初始化、把UI首次渲染需要的数据提前预加载、避免首帧时做大量主线程IO。笔试会出一个"首屏Feed流从点击图标到出现商品列表,整个过程有哪几个耗时节点"的题,你如果只回答"启动+网络请求+Cell布局"就太泛了,需要细化到:进程创建、动态库加载、runtime初始化、main函数调用、application:didFinishLaunching回调、首页VC加载、请求发送、数据解析、Cell显示。
6. 算法与数据结构:笔试里常见的几类题,以及怎么用OC写不别扭
6.1 字符串类题目:最长回文子串、字符串反转、大数相加
京东笔试的算法题不会特别难,但都集中在高频类型。字符串处理是必须准备的。我记得有一道题是让用OC实现一个字符串反转,并说明时间复杂度。看似简单,但很多人会忽略OC里NSString的不可变性和字符编码问题。如果用C语言的char数组逐个交换,要注意中文等多字节字符的处理;如果直接用OC的substringWithRange:做反转,倒也能完成,但要注意循环里反复创建子字符串的性能浪费。
大数相加是另一个常见题,两个很长的数字字符串相加,不能用LongLong直接算。正确做法是模拟手工加法,从最低位开始逐位相加,处理好进位。这道题测的是逻辑清晰度和边界条件处理,我见过不少同学在一次遍历里因为进位标记没复位导致答案错一位。
6.2 链表和二叉树:OC里面手写数据结构需要刻意练习
OC写链表和二叉树确实比C++或Java要绕一些,主要是因为nil和NULL的边界处理、对象类型的强引用和弱引用问题。笔试里链表题通常是反转链表、链表环检测、合并两个有序链表。二叉树题则集中在层序遍历、最近公共祖先、路径总和这几类。
用OC写的时候有个细节特别容易被忽略:节点类里的next指针如果用strong修饰,容易在处理环形链表时造成循环引用和内存泄漏。所以我写这类代码时,节点属性会刻意用assign或weak,并在需要断开链接时手动置nil。
6.3 Top K问题和海量数据的降级策略:电商场景的算法包装
Top K问题经常被包装成"从10万个商品中找出价格最高的100个商品""从百万级浏览记录中找出被访问最多的10个页面"。最经典的解法是用大小为K的最小堆,这样每次只需要和堆顶比较,大于堆顶就替换并调整堆,时间复杂度是O(N log K),空间复杂度是O(K)。如果用排序做,时间复杂度O(N log N),在大数据量下明显吃亏。
笔试如果给出内存限制,比如"10亿个数找最大的100个,内存只能容纳1000个数",你需要答出分治和堆结合的思路,先把数据分片,每片求出各自的Top K,再做归并。这种题其实不太考验OC语法,更考验你是否具备海量数据处理的工程思维。
6.4 动态规划和贪心:不是重点,但不能完全不会
京东笔试的算法题整体偏应用,动态规划的比例不像字节那么高,但也会出现简单到中等的题目,比如爬楼梯、最大子序和、零钱兑换。贪心作为辅助类型也偶尔出现。我倾向于把这类题放在最后做,先保证字符串、链表、二叉树、Top K这些高频题目全对,再回头啃DP。
动态规划从笔试得分角度来看,重点不是你能不能在10分钟内AC,而是你能不能描述清楚状态转移方程。我当时在答题纸上先写"dp[i]表示前i个元素能组成的最大子序和,转移方程是dp[i] = max(dp[i-1] + nums[i], nums[i])",再写代码,这样即使代码有小bug,阅卷人也知道你的思路是对的。
7. 备考京东iOS笔试,我踩过的坑和最终总结
京东2019校招笔试整体难度中等偏上,相比纯刷题型大厂,它更像"技术原理 + 业务场景 + 算法基本功"的综合测试。我当年准备时走过不少弯路,分享几个关键教训。
第一,不要只刷题不读源码。Runtime、内存管理、GCD这些部分,只看别人总结的面试题永远搞不透。建议至少读一遍objc源码里关于对象生命周期和弱引用的实现,哪怕不追求每个函数都懂,把关键流程的骨架弄清楚,笔试里那些"为什么"类题目就能答出层次感。
第二,笔试前一定要动手写几个完整的UI性能优化Demo。不要纸上谈兵,用Instruments跑一遍卡顿和离屏渲染检测,看真实数据。京东笔试里如果出优化题,你可以引用自己测试出来的数据,比如"我手动测试过,用异步绘制之后列表帧率从45提升到58",这种答题比空洞的背条目有说服力十倍。
第三,算法题准备要有侧重点。如果目标明确是电商大厂,多关注字符串、数组、链表、二叉树、Top K、动态规划这几个类型。OC语言的语法特性会让算法实现比C++稍微繁琐,建议在笔试前把常用数据结构都用OC手写一遍,尤其是链表类和二叉树类,避免考场上因为语法卡壳影响思路。
第四,审题要仔细,尤其是那些"以下说法正确的是"和"以下说法错误的是"这种选择题。京东特别喜欢在一道题里埋两个反向选项,比如"atomic能保证多线程访问绝对安全"和"atomic只能保证getter/setter的线程安全",看起来很像,但答案完全不同。我当时有两次都差点选错,最后是靠对细节的精确把控救回来的。
最后想说,笔试只是校招的一道门槛,真正决定offer的往往是后面的技术面。但笔试里考察的知识点,恰恰是技术面深挖的起点。把笔试准备扎实了,后面的路会顺畅很多。如果这篇文章对你有帮助,或者你对其中某个知识点有不同理解,欢迎在评论区交流,一起查漏补缺。