十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

京东iOS春招笔试复盘:内存管理到架构设计考点全解析

京东iOS春招笔试复盘:内存管理到架构设计考点全解析 2019年春招那会儿我投了京东的iOS开发岗。笔试通知来得比想象中早点开链接看到整套卷子的第一反应是选择题量不小后面还跟着手写代码题和一道开放式的方案设计题。考完之后我把整张卷子复盘了一遍最大的感受是——它不像学校期末考试那样考你“记没记住”而是用选择题试你的基础边界用编程题试你的工程习惯再用一道设计题看你在真实业务里能不能做取舍。这套卷子虽然挂在2019年名下但里面的考点放到今天依然是iOS面试的高频区域面试题换了一层皮底层逻辑没变过。这篇复盘我就按自己的记忆把卷子里涉及的几个核心模块拆开讲清楚包括每道题背后的考点、当时我为什么这么答、后来查资料发现更优解在哪。如果你是准备iOS面试的开发者不管是校招还是社招这套题的知识版图都有很强的参考价值。整理得比较细建议收藏慢慢看。1. 一场春招笔试的标本价值整套卷子到底在筛什么人先说一个很多人容易误解的地方大厂校招笔试题的定位从来不是“选拔写代码最强的人”而是“在最短时间内筛掉基础不过关的人”。京东2019春招这套iOS开发卷子也是这样整体难度梯度拉得非常明显前段选择题覆盖面广但偏基础中段开始出现需要综合判断的题末段的手写代码和设计题才是真正的分水岭。整套卷子的知识模块分布我凭记忆整理如下今天的iOS面试考点版图也基本是这个圈知识模块典型考察方式大致占比Objective-C / Swift语言基础选择题属性修饰符、内存管理、Block、Runtime25%UIKit与布局选择题手写AutoLayout、UIStackView、事件传递链15%网络层选择题HTTP/HTTPS、TCP/IP、加密与证书10%并发编程选择题手写GCD、NSOperation、线程安全15%架构与设计模式设计题MVC/MVVM/组件化、消息模块设计15%性能优化选择题场景题卡顿、内存泄漏、电池优化10%算法基础手写题二分、链表、动态规划10%这个比例不是随便拍的它反映了大厂对一名合格iOS开发者的基础预期不是某个方向的专家而是一个能独立负责业务模块的“六边形战士”。语言层面你得把内存管理的坑摸清楚UI层面你得知道复杂页面怎么拆约束网络层面你得能解释清楚为什么HTTPS握手是四次并发层面你得具备写线程安全代码的本能反应。这些能力不是背题背出来的是平时写代码和查文档查出来的。另外这套卷子有一个很鲜明的特点就是“看着在考A实际上在考B”。比如有一道题表面问的是“UIStackView和AutoLayout的关系”实际想看的是你有没有在复杂布局场景里选错工具的判断力。我把这个特点单独放到后面一章细讲因为这才是面试官真正的判卷逻辑。2. 真题复盘从基础题到开放题逐题拆解答题思路2.1 语言基础题内存管理不是背修饰符是预判对象什么时候释放开场就是几道Objective-C基础选择题印象最深的一道给出一段代码问某个自定义对象在哪个时机被释放选项里有viewDidLoad结束、viewWillAppear结束、点击事件处理完毕、下一个Runloop周期等。这道题不少人栽了因为表面考“对象释放时机”实际考的是“自动释放池的边界”。我的答案思路是先把对象的持有关系画清楚如果这个对象是用alloc/init创建的强引用那么在方法作用域结束时就该释放跟Runloop没关系但如果是通过[NSString stringWithFormat:]这类方法返回的autorelease对象释放时机就会推迟到当前自动释放池被drain的时候。在iOS的主线程Runloop里一个Runloop周期结束才会drain一次释放池所以某些情况下你会看到对象在“下一轮Runloop”才真正调dealloc。顺着这条线我把相关的修改符也重新理了一遍。strong和weak的区别大家都会背但笔试真正想看到的是weak修饰的对象在释放时会被自动置为nil这背后是Runtime对Weak表的管理不是简单的指针赋值。如果你能额外提一句“SideTable里存了对象的弱引用指针数组释放时遍历数组把所有weak指针置nil”这道题的深度立刻就不一样了。Swift这边也出了一道和可选类型相关的题一个字典取值返回的是Value?如果一个函数接收非可选参数怎么处理才能保证安全。常规答案是guard let或if let解包但更完整的答法会提到用??提供默认值以及guard与if let的作用域差异。我当时把这两种思路都写了附了一句“优先用guard因为它让异常分支早退出主逻辑保持扁平可读性更好”。2.2 UIKit与布局题UIStackView不是银弹但很多人连弹药都没有UI布局这块卷子里直接给了一道很实操的选择题在iOS 11及以上系统要把三个不同高度的视图水平排列并保持等间距你会优先选择什么方案。选项包括Frame计算、AutoLayout约束、UIStackView、手动计算坐标。这道题我答的是UIStackView原因不只是它“代码量少”而是它自带Distribution和Spacing的语义对间距的维护成本远低于手动添加NSLayoutConstraint。但请注意UIStackView有一个容易踩的坑也是面试官想听你展开的它本质是“对子视图约束的封装”并不是把子视图的布局问题完全消灭了。比如你要在UIStackView里放一个高度需要自适应文字内容的UILabel此时如果同时给Label加了内容压缩阻力相关的约束容易出现约束冲突或布局不稳定的问题。实际开发里你还需要搭配setContentHuggingPriority和setContentCompressionResistancePriority来控制“谁拉伸谁压缩”。用代码表达就是这样的思路let stackView UIStackView(arrangedSubviews: [titleLabel, contentLabel, footerView]) stackView.axis .vertical stackView.alignment .fill stackView.distribution .fill stackView.spacing 12 contentLabel.setContentCompressionResistancePriority(.required, for: .vertical) titleLabel.setContentHuggingPriority(.defaultHigh, for: .vertical)这段代码里最关键的是最后两行它们决定了当空间不够的时候哪个视图先被压缩哪个视图必须完整展示。不加这两行StackView也会工作但遇到长文本内容时布局就会“抽风”这是我实际开发里踩过的坑也是笔试题背后隐含的深水区。另外卷子里还有一道和事件传递相关的题点击一个subview系统是怎么一步步找到响应者的。这个考察的是UIView的hitTest和事件响应链。我当时答的时候把两个核心方法都列了出来系统先调用hitTest:withEvent:从最外层Window开始向下递归找命中的视图找到之后事件再从命中视图开始向上沿响应链传递直到有一个响应者处理。这道题的重点其实是“寻找过程和响应过程的顺序是相反的”一句话点透面试官就知道你是真懂。2.3 网络层HTTP和HTTPS的区别不能只答“多了一层SSL”网络相关的题里有一道是简述HTTPS的握手过程这也是iOS面试的万年老题。很多人一上来就说“HTTPS HTTP SSL”这句话没错但只能拿基础分。我当时的答法是拆成四步客户端发起HTTPS请求携带支持的TLS版本、加密套件列表、随机数。服务端返回自己的数字证书、选定的加密套件、另一个随机数。客户端验证证书的有效性信任链、过期时间、域名匹配通过后生成预主密钥用服务端证书的公钥加密后发给服务端。双方各自用随机数预主密钥算出相同的会话密钥之后进入对称加密通信。这样回答的核心价值在于它把“加密套件协商、证书校验、密钥交换、会话密钥生成”这几个阶段说清楚了。面试题真正想考察的是你对“非对称加密只用来交换密钥对称加密才用来传输数据”背后的性能考量有没有认知。因为非对称加密速度慢、开销大不适合大批量数据传输HTTPS只有在握手阶段用非对称加密交换密钥后续都用对称加密。和网络层相关的另一道实操题是线上环境某个接口在部分用户手机上A/B测试结果不稳定怎么排查。这题标准解法是先看是不是运营商DNS问题或代理问题然后打开Charles抓包对比正常和异常请求的Header。Charles抓包在iOS开发里几乎是必修课尤其是同事反馈“我这个页面请求失败别人没事”时抓包能快速定位是请求参数问题、证书信任问题还是返回数据解析问题。iOS上抓HTTPS包需要先安装Charles的根证书并信任iOS 10以后还要在“设置-通用-关于本机-证书信任设置”里手动开启完全信任这个步骤经常被新手漏掉。顺手分享一个查耗时问题的技巧用Charles的Timing标签页看各个阶段耗时重点看Connection和TTFB。如果TTFB普遍偏高问题多半在服务端如果Connection耗时高可能是客户端DNS或网络链路问题如果只有特定接口慢再从接口入参、加解密逻辑上去排查。2.4 并发与线程安全手写安全数组关键在锁的粒度并发题在整套卷子里占的比重不小其中一道手写题是实现一个线程安全的可变数组。这题看起来简单但大多数人写出来的版本都有问题。第一版典型的错误答案是interface SafeArray : NSObject property (nonatomic, strong) NSMutableArray *array; end - (void)addObject:(id)obj { [self.array addObject:obj]; }这个版本的问题非常明显atomic修饰属性只保证属性的getter/setter是原子的也就是读写指针本身不会出现数据竞争但对你“先读数组状态再修改数组内容”这一整个操作是没有任何保护的。并发环境下两个线程同时调用addObject:底层仍然是同一个NSMutableArray在被两个线程同时修改崩溃几乎是一定的。加分答案是在内部加锁核心在于锁的粒度要覆盖“整个修改操作”interface SafeArray : NSObject property (nonatomic, strong) NSMutableArray *array; property (nonatomic, strong) NSRecursiveLock *lock; end - (void)addObject:(id)obj { [self.lock lock]; [self.array addObject:obj]; [self.lock unlock]; }这里用NSRecursiveLock而不是普通NSLock是因为如果后续某个方法内部又调用了本类加锁的方法递归锁允许同一线程重入能避免死锁。或者用GCD串行队列做同步也完全可以- (void)addObject:(id)obj { dispatch_sync(self.syncQueue, ^{ [self.array addObject:obj]; }); }我当时这题答的是GCD方案并且在旁边写了一句读操作可以用并行队列写操作用barrier这样能进一步优化性能。后来查资料确认dispatch_barrier_async确实是读写锁在GCD层面的理想实现。面试官看到这种答案基本就能确认你不仅会用锁还考虑过并发场景下的性能权衡。2.5 架构设计题不是让你画漂亮的图是看你怎么拆职责开放题部分有一道很有京东业务特色的设计题设计一个电商App购物车模块的架构要把“App启动后自动读取上次未提交的商品”“商品价格变化时实时刷新”“本地缓存与服务端同步冲突的处理”都覆盖到。这种题没有标准答案但评卷时有明确的层次差异。我当时的答题框架分了三层数据层用一个CartStore单例统一管理购物车数据内存态用可变字典存储磁盘态用数据库做持久化服务层封装了CartRepository负责拉取服务端购物车、推送本地变更、同步冲突的合并策略UI层走MVVMCartViewModel把领域模型转化成Cell的显示模型页面只和ViewModel交互。这套结构在面试里很常见关键是后面的细节。对比了两个主流方案的取舍这是设计题真正的得分点数据统一走内存每次App冷启动从数据库恢复但从数据库恢复只能保证“本地兜底”服务端价格变更还是得靠网络拉取所以必须设计一个“版本号更新时间戳”的增量同步机制。价格变化实时刷新不能只靠用户在页面停留时收到推送因为App可能在前台但用户没停留购物车页此时只需要更新缓存和角标不需要刷新UI。这就对“数据变更通知”做了分级同一套数据要同时驱动UI更新和非UI逻辑。我当时写的同步冲突策略是“时间戳优先但保留原始版本供用户选择”。具体来说如果本地修改时间晚于服务端修改时间以本地为准并推送到服务端反之以服务端为准并覆盖本地。如果两端修改时间非常接近比如差距小于1秒才标记为冲突把服务端版本设为默认但弹提示让用户确认。这套逻辑不是完美方案但它展示了“在不确定的情况下如何设计兜底策略”而这就是开放题真正想看到的工程判断力。2.6 底层机制题RunLoop和Runtime不背源码也要懂设计动机这套卷子里关于Runtime和RunLoop的题都出现了而且都藏在比较靠后的位置难度明显比前面的语言基础题高。有一道是解释为什么NSTimer在UIScrollView拖动时会暂停回调。这个现象我在入行初期确实遇到过当时的第一反应是“系统调度到其他线程了”后来才发现完全不是是RunLoop的Mode切换导致的。主线程的RunLoop跑在kCFRunLoopDefaultMode下NSTimer默认也添加在这个Mode里。当用户拖动ScrollView时RunLoop会切到UITrackingRunLoopMode这个Mode下有且只有和滚动事件相关的source/observerDefaultMode下的Timer就被暂时挂起了。等滚动结束RunLoop切回DefaultModeTimer重新恢复回调。解法有两种把Timer手动添加到NSRunLoopCommonModes这样它能同时被Default和Tracking两个Mode共享或者干脆用高精度无Mode依赖的CADisplayLink但CADisplayLink不适合做长时间定时。Runtime那一道则是给了一段交换两个方法实现的代码问执行后调用方的行为。这个考的就是method_exchangeImplementations的副作用。在做Method Swizzling时最常见的问题是两个方法交换后如果双方内部都调用了[self originalMethod]会造成无限递归。这也是我后来封装工具类时特别小心的地方每次Swizzle前先判断方法是否存在交换后用一个静态布尔变量做重入保护。这种题目单看都是零散的知识点但放在同一套卷子里其实是面试官在测试你对“iOS底层运行机制”的系统性认知。你要理解RunLoop是iOS事件循环的引擎理解Runtime是Objective-C动态特性的基石并能在脑子里把它们组织成一张网。单纯背结论是扛不住追问的。2.7 算法与场景题手写二分、链表反转之外还有业务逻辑设计京东的算法题不算特别难我印象里有一道“二分查找有序数组中目标值最后一次出现的位置”和一道“单链表反转”。这类题目属于数据结构的基本功准备面试之前刷一刷LeetCode的热题100基本能覆盖个七八成。但有一道业务场景题我当时觉得挺有意思设计一个多选规格互斥的判定逻辑简化版就是购物车中用户选择了手机颜色、版本、优惠套餐某些搭配不允许提交要在前端最早拦截。这道题考的不是某种算法模板而是如何把一个业务规则抽象成代码结构。我当时的思路是把“合法的组合”定义为一个白名单表每次用户切换选项时都在内存中维护的规则表里查一次命中合法组合才允许加入购物车。如果组合非法前端直接Toast提示并且保留上一次合法选择。这个方案的优点是规则修改时只改配置表不用动页面逻辑方便后续接入后台动态下发。面试官追问“如果规则表很大怎么办”我的回答是“把规则表按类别分组用字典索引先根据当前选择筛掉不相关的分组”。这道题给我的启示是大厂笔试的算法题不光看你能不能写出快排更看你在业务约束下能不能选择合理的数据结构。3. 那些容易丢分的暗坑判卷逻辑与高频失分点3.1 表面考A、实际考B的“概念陷阱”整套卷子最阴险的题是那些看起来在问一个概念、实际在考你对边界情况的判断力。举一个典型的例子题目问“copy修饰一个NSMutableArray属性赋值一个可变数组后这个属性是可变还是不可变”。正确答案是“不可变”因为copy会返回一个不可变副本属性类型虽然是NSMutableArray但运行时指向的对象是不可变的如果后续调用addObject:会直接崩溃。这种题的失分点在于很多人记住了“copy返回不可变对象”这个结论但没有想过它对实际代码的影响。我后来在团队代码评审时遇到过一模一样的惨案——一个同事用property (nonatomic, copy) NSMutableArray *cartList;然后所有的地方都在直接对cartList做增删操作线上崩溃率一下子蹿高。所以笔试考这种题一点都不超纲它考的就是你有没有在真实项目中用自己的手踩过坑。另一个暗坑类型是“答案选A最优解选B”的陷阱。比如UIStackView那题用AutoLayout手动加约束也能实现等间距效果功能上没毛病但维护成本高、代码量翻倍。面试官设置这种选项是想看你有没有“根据场景选方案”的意识。我在改卷时看到不少同学选了“自动布局等宽约束”如果这是一道多选题这个选项不算错但它出现在“你会优先选择什么方案”的题干里就一定要结合可维护性来判断不能只看“能不能跑通”。3.2 手写代码题的隐性要求编译不过、命名混乱、边界漏判笔试中的手写代码题通常不会要求你写出一个能直接编译运行的完整工程但评卷人会模拟“编译你的代码”的视角去看。有两个经常被忽略的硬伤命名不规范和边界照顾不周。比如我见过一份答案写二分查找思路完全正确但循环条件写的是while (left right)而查找目标值最后一次出现的位置需要while (left right)这种细节。边界条件错一个符号整个返回值就是错的。还有写单例的题标准写法要考虑dispatch_once、线程安全、allocWithZone拦截但不少人只写了staticsharedInstance方法忽略了copyWithZone和init的拦截。面试时追问一句“如果有人调用[[MyClass alloc] init]拿到的还是同一个单例吗”很多人当场就懵了。我的建议是手写题除了把核心逻辑写对还要习惯性地补上边界检查和防御性代码。不用特别复杂但一定要让评卷人看出你有“处理异常输入”的意识。比如二分查找前面加一行if nums.count 0 return -1链表反转循环里注意空指针这些细节不会增加多少代码量却能让你的卷面从“会算法”升级到“会写工程代码”。3.3 设计题的核心失分点只有方案没有取舍设计题是整套卷子区分度最高的一道题因为基础题大家卷面都差不多设计题一下就能拉开差距。我观察到的普遍失分点是只写“我用了什么框架”或者“我分了几层”完全没提“为什么这么分”和“不这么做会怎样”。这就像你面试时说“我用MVVM”但他说不出MVVM相比MVC到底解决了哪一类痛点也说不出引入MVVM后带来了哪些新的复杂度。我自己回答设计题的习惯是“先抛约束再给方案”。具体说第一句先承认“购物车模块没有一劳永逸的架构”然后说明我面对的主要约束是什么比如“首页和购物车页都需要读取同一份购物车数据所以数据层必须独立成单例不能把数据挂在某个ViewController上”然后再说“约束定了方案自然就定了数据层单例服务层仓库UI层MVVM”。这样答题的逻辑闭合性很强面试官会觉得你不是在背模板而是在真实场景里做判断。另一个容易被忽视的得分点是主动说明方案的“反模式边界”比如“如果购物车页面只有一个入口、数据不会被其他页面改动那就不需要额外引入状态管理框架避免过度设计”。主动谈“哪些情况下不需要复杂设计”比单纯堆方案更显水平。4. 从试卷反推的备考路线图如果现在让我重新准备一次4.1 基础层怎么补以“能解释清原理”为终点不背概念准备笔试最容易陷入的误区是刷了无数题但每一道题都是“看得懂答案说不出为什么”。这套卷子给我的最大提示是基础题虽然多但你把题目换个问法比如从“copy修饰NSMutableArray会怎样”变成“开发中遇到unrecognized selector崩溃你怎么根因分析”如果能答出是“对象被错误地copy成不可变类型”那你才是真懂了。我建议花一整块时间把以下基础模块过一遍以“能讲给别人听”为标准Objective-C属性修饰符、ARC和MRC、Block的原理与循环引用、Runtime消息转发机制、KVO/KVC的原理与坑。Swift值类型与引用类型的差异、闭包捕获列表、内存管理、泛型使用、可选链。UIKitView生命周期、ViewController生命周期、事件传递链、AutoLayout和Frame的取舍、UIStackView、UICollectionView的缓存复用机制。网络TCP三次握手、HTTP/HTTPS、DNS解析、Charles抓包、AFNetworking/NSURLSession源码的初步理解。这一块如果你已经有工作经验可以从自己写过的代码和踩过的坑反推。我当时补RunLoop就是直接从“列表滚动卡顿、Timer不回调”的真实问题入手比硬啃网上的源码分析效率高很多。4.2 项目经历怎么包装把“做过”翻译成“解决了什么”笔试过了之后还有面试而面试环节一定会围绕项目经历深挖。我特别想提醒的是不要只报“我在项目里负责了XX模块开发”这种流水账而是要有意识地用“问题-方案-验证”三段式去讲。比如你说自己做过“首页Feed流优化”可以这样组织问题列表快速滑动时出现明显卡顿帧率掉到40FPS左右。方案和思考先用Instruments的Core Animation工具定位是CPU还是GPU瓶颈发现是Cell中大量使用圆角阴影导致离屏渲染过多。方案是改用CoreGraphics绘制背景图代替layer阴影。验证优化后用Xcode的FPS检测稳定在58-60FPS再用Time Profiler确认主线程耗时下降。京东的面试官非常喜欢问“边界情况你怎么处理”所以你在项目里最好自己能提前准备几个边界场景。比如商品价格从100降到10块购物车里已经加入了这个商品UI、缓存、推送、数据同步各环节分别怎么处理。这种问题在笔试设计题里就是拿分点在面试里同样也是加分项。4.3 备考节奏和工具链建设简历项目、算法、刷题书怎么平衡笔试和面试的准备时间通常是有限的我的体感是把时间分成三块40%补基础模块尤其是内存管理和UI机制、30%刷算法题、30%整理项目叙述。算法题不用贪多每天2-3道经典题按“数组、链表、树、动态规划、字符串”分类刷比盲目刷300道有效得多。如果你是准备社招我额外建议把“工程化”能力也纳入准备范围iOS证书与描述文件的更新流程、TestFlight打包分发、CI/CD自动化构建、组件化或二进制化方案。我见过不少笔试基础很扎实的同学挂在“你说说你们App的证书是怎么管理的”这种实战细节上。工作越久面试官越看你有没有全局工程视角。工具链方面模拟器和真机调试的差异也要专门过一遍。模拟器因为没有真实网络芯片和硬件传感器很多性能问题和定位问题在模拟器上无法复现。如果你在简历里写了“精通性能优化”面试官大概率会追问“你是用真机调的还是模拟器调的”这个回答质量很能反映真实经历。4.4 混合开发与新技术方向当年没考但今天绕不开2019年的卷子里还没有太多跨端开发的题目但如果今天的面试官问起大概率会涉及混合开发方案和跨平台框架。你至少要能说清楚WkWebView的JSBridge通信原理、React Native的JS线程与原生线程的通信方式、Flutter的Skia渲染管线以及这些方案分别在什么业务场景下更合适。我自己的经验是跨端方案最核心的面试问题不是“你会不会用”而是“你知不知道它的性能边界在哪儿”。比如WebView方案在低端Android机上的列表滚动帧率上不去这是WebView引擎的渲染机制决定的不是调优能解决的Flutter的优势是自绘引擎保证了UI一致性但遇到原生地图、相机这类需要原生能力的场景就得写Platform Channel原生插件。一旦你能用“场景匹配”的视角去聊技术选型面试官就不会觉得你只是个会调API的开发者。5. 写在最后的复习小建议这套卷子复盘到这里我想说的是一份考卷不是一个知识点清单它更像一面镜子照出你日常写代码时的习惯和盲区。如果你发现基础部分错得比较多大概率不是刷题不够而是平时写代码时“能跑就行”的心态太重。我自己早期也是那种“加了个copy修饰符就觉得很稳妥”的开发者直到真的被线上崩溃教育过一次才明白这些看似无聊的修饰符背后都是血泪。如果你准备时间有限我建议优先把内存管理、RunLoop、响应链这三块吃透它们是iOS面试的“三座大山”也是这套卷子占比最高的部分。每看完一个知识点都试着用一句话解释给一个完全不懂iOS的人听如果说不清楚说明你自己也还没真懂。最后一个小技巧笔试遇到不确认的题不要空着尽量写“我会怎么排查”的思路哪怕最终答案错了判卷人也能看到你解决问题的路径。大厂要的不是一台活体题库而是一个知道如何面对不确定问题的人。
返回列表