ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

2023小满春招iOS笔试复盘:从Runtime到架构设计的核心考点解析

2023小满春招iOS笔试复盘:从Runtime到架构设计的核心考点解析 做了这么多年iOS开发每年春招秋招都会帮团队出题或者面试候选人。2023年小满这条线我印象挺深第三批笔试整体难度比前两批高了一截不再只问语法和API调用开始往原理、架构和实战排障方向倾斜。今天把这批笔试的复盘整理出来从考点拆解到答题思路再到我后来对照候选人反馈补的一些经验一次性聊透。无论你是正要投实习的在校生还是工作两三年想跳槽的同行这份内容都能帮你把iOS知识体系重新梳理一遍。1. 2023年度小满春招iOS研发岗第三批笔试整体设计与考点拆解1.1 批次定位与题目设置逻辑第三批笔试安排在春招中后段这个时间点的候选人通常已经过了一轮简历筛选和部分电话面试所以笔试不再考察“知不知道”而是考察“懂不懂”。整张卷子分为六个模块Objective-C与Runtime、Swift与iOS内存管理、UIKit布局与AOT、网络与数据持久化、多线程与并发、架构设计。每模块约三到四道题题型以简答加场景设计为主部分题目要求直接写代码或补全代码。从出题逻辑来看这套笔试明显参考了当时字节、美团、快手等一线大厂的中级开发考核标准。相比前两批增加了一个很关键的变化所有题目都带了一个“场景壳”。比如不直接问“block有几种类型”而是问“在NSOperationQueue的block中修改外部局部变量为什么报错如何修改”。这种问法对候选人很考验因为背八股文的人容易答非所问真正写过代码的人反而能快速定位到__block关键字。1.2 需要提前掌握的知识体系结合我这些年的面试经验和实际带团队的经历备考这套笔试前至少要摸清以下知识框架Objective-C内存管理ARC/MRC、循环引用、weak底层原理、autoreleasepool、dealloc时序iOS运行时体系isa指针、类对象与元类、消息发送/转发、关联对象、Method Swizzling、KVO底层实现Swift与OC的差异化记忆值类型与引用类型、协议派发、String/Array底层、闭包捕获列表UIKit核心机制AutoLayout约束计算、UIStackView、UICollectionView自定义布局、离屏渲染、UIView和CALayer关系网络与存储HTTP/HTTPS握手、NSURLSession、DNS解析、ATS、SQLite与CoreData选型、沙盒目录并发编程GCD、NSOperation、线程锁、内存屏障、并发队列和串行队列、dispatch_group/信号量架构与工程化MVC/MVVM/组件化、模块化、大产品性能监控、崩溃分析、包体积优化下图是我自己梳理的各模块分值占比方便做复习时间分配。2. 核心细节解析Objective-C、Swift与内存管理高频考点2.1 消息机制与isa指针的底层逻辑这批笔试中有一道让我印象深刻的题“解释实例方法、类方法分别存在哪里消息发送的关键路径是什么”。这题看起来基础实际很考验掌握深度。很多人知道方法存在类对象里但说不清类方法存在元类里更难说清对象object_getClass(obj)和obj.class的微妙区别。实例方法存在对象的类对象中即obj-isa指向的Class结构中这个Class的方法列表存储可供该实例调用的实例方法。类方法存在元类中。调用[SomeClass classMethod]时底层走的是objc_msgSend(objc_getMetaClass(SomeClass), selector(classMethod))真正接收消息的主体是元类对象而不是类对象本身。如果没有命中缓存和方法列表会走消息转发三件套resolveInstanceMethod、forwardingTargetForSelector、forwardInvocation。实际开发中用到这套知识最多的场景是热修方案、防崩溃SDK设计和AOP埋点。比如我做崩溃防护SDK时就是利用forwardInvocation把未识别的方法安全吞掉避免线上Crash。这个方法需要特别注意一点如果同时做了Method Swizzling务必要保证IMP的线程安全。2.2 Autoreleasepool与dealloc时序误区很多候选人拿到“自动释放池什么时机释放”这道题张嘴就是“runloop循环结束”严格说这是不对的。更准确的说法是在没有显式创建autoreleasepool的情况下系统会在线程的runloop每次迭代结束时自动创建和释放一个自动释放池。但注意释放池的释放并不是runloop退出而是runloop进入sleep之前、处理事件源之后发生的。__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__等Observer回调批次就会触发_objc_autoreleasePoolPop。另外还有个高频误区dealloc中不能调用属性的setter方法。原因是对象处于正在释放状态setter有可能触发KVO通知引起额外复用的风险。正确做法是直接对实例变量做置空操作。2.3 Swift闭包捕获列表与循环引用的场景案例这轮笔试有一道代码题考的是Swift中UIView动画闭包的内存管理。题目大意是在一个UIViewController里持有NSObject子类的实例并在该实例的闭包里使用self问会不会循环引用、怎么解决。答案除了常见的[weak self]还要注意一个细节如果只是单次执行的闭包比如UIView.animate闭包执行完会释放就算用self也不会循环引用但若是存储型闭包比如属性里面保存了一个闭包那必须用捕获列表打破循环。很多候选人忽略了这个前提直接答weak实际对场景判断是有缺陷的。Swift闭包捕获列表还有另一个坑如果捕获列表里写了[unowned self]而self在闭包执行前已经释放会直接崩溃。实战中我遇到过多起unowned导致的闪退排查方式是在崩溃堆栈里看到SIGSEGV和闭包关键字基本就能锁定。3. 实操过程与核心环节实现UI、网络与存储题解3.1 UIKit布局与AutoLayout自适应方案“UIStackView在动态列表中的使用”是这套笔试的一道重点题。实际开发中UIStackView确实能极大简化约束但它也有两个容易踩的坑在UIScrollView里使用UIStackView时如果stackView的宽度需要跟随内容变化必须给stackView设置完整的约束链否则内容无法驱动contentSize。UIStackView的distribution选择。fillEqually会让所有子视图宽度一致但如果某个子视图内部有需要撑满的label容易发生压缩或约束报错。正确的做法是根据内容优先级设置setContentHuggingPriority和setContentCompressionResistancePriority。我当时给候选人的加分点是能说出用UIStackView配合UICollectionView利用系统提供的UICollectionViewCompositionalLayout进行更精细的自适应布局。这是iOS 13之后推荐的做法也是对大型列表最友好的方案。3.2 网络层从AFNetworking底层到ATS配置笔试里的网络题不再是背HTTP状态码而是考“HTTPS在iOS下如何进行证书校验”和“AFNetworking的证书校验过程”。比较完整的回答流程是系统层面NSURLSession在TLS握手阶段会先做服务端证书链校验如果证书无效直接报错。业务层面可以自定义URLSession:didReceiveChallenge:completionHandler:在代理方法里对challenge做二次校验比如校验证书公钥、域名、甚至证书Hash。如果使用AFNetworking它的核心配置在AFSecurityPolicy默认是AFSSLPinningModeNone也就是只做系统校验想要严格校验可以设置为AFSSLPinningModeCertificate或AFSSLPinningModePublicKey。还有一道ATS相关的题为什么iOS9之后要配置Info.plist里的NSAppTransportSecurity。这个是为了强制HTTPS连接。但要注意不要为了图省事直接NSAllowsArbitraryLoads设为trueApp Store审核有较大概率被拒。合理的做法是只对需要兼容的第三方域名做例外处理并且限制在Debug环境。3.3 磁盘缓存、数据库选型与沙盒目录解析这轮笔试还考了一个实战问题“图片缓存和列表数据缓存分别用什么方案”。最佳实践是分三层内存缓存NSCache注意它和NSDictionary的区别是当内存紧张时系统会自动回收部分缓存。NSCache不是线程安全的但它是系统封装的线程安全版本性能比手动加锁好很多。磁盘小文件基于FileManager按路径存储适合放用户头像等小图但需要自己控制淘汰策略。数据库适合存储结构化数据比如消息记录、离线缓存。如果数据量大且查询复杂选SQLite如果数据量小并且偏好使用ORM可以选CoreData。要注意CoreData在多线程场景下必须使用私有上下文或主上下文共享模式否则会报Crashes。我建议所有候选人都能把沙盒目录回答清楚目录用途是否会被系统备份Documents用户文档、重要数据是Library/Caches缓存数据可重新下载否Library/Preferences偏好设置是tmp临时文件否笔试题目通常会问“哪些数据会被同步到iCloud”这正是坑点所在。4. 并发编程、RunLoop与安全机制实战解析4.1 多线程锁选择从os_unfair_lock到性能对比笔试中遇到多线程题最常见的陷阱就是让候选人解释“自旋锁和互斥锁的区别”。虽然OSSpinLock已经在iOS 10被废弃但很多老项目还在用需要清楚它的优先级反转问题和替代方案。os_unfair_lock苹果推荐的替代自旋锁的锁如果线程未获锁会进入休眠不会忙等。pthread_mutexPOSIX标准互斥锁支持递归锁和条件变量适合复杂多线程同步。NSLock是对pthread_mutex的OC封装效率稍低于直接用底层的。synchronized使用对象作为递归锁方便但代价较高尤其是性能要求高的场景不能滥用。dispatch_semaphore信号量常用于线程数量控制。NSRecursiveLock解决同一线程重复加锁导致死锁的问题。实际排障时我会先用Instruments的Thread Sanitizer进行检测。它能在编译期和运行期检测出数据竞争然后根据崩溃堆栈判断锁范围内是否有IO操作或耗时操作。如果发现锁内做了数据库查询那性能一定上不去正确方案是锁外提前处理数据。4.2 RunLoop与线程保活的完整流程题目上画了一个图要求标注RunLoop在哪个阶段处理Timer、在哪个阶段处理Source。真实开发里我最常用RunLoop的地方有两个高性能图片加载。在主线程滑动列表时通过CFRunLoopAddObserver监听即将进入休眠的状态并在该状态批量加载剩余图片从而保证滑动帧率不掉。常驻后台线程。用NSThread做长时间任务时给线程添加一个source或timer让runloop存活避免线程执行完就被回收。补充一个坑RunLoop不是线程安全的跨线程操作时必须加锁并且只能操作处于kCFRunLoopCommonModes的mode。4.3 iOS签名与证书机制、上架与内部发布流程这部分笔试不是问怎么在开发者后台点按钮而是考察背后的原理。完整流程如下开发者证书生成CSR文件并上传到Apple开发者中心经过Apple后台签名后生成.cer证书。此证书包含了开发者的公钥私钥保留在Mac的钥匙串中。App ID和Entitlements在开发者中心注册App ID并配置能力例如推送、iCloud等。Provisioning Profile描述文件将证书、App ID和设备UDID绑定生成.mobileprovision文件打包时嵌入到App中。代码签名产物通过codesign将上述组合签名到App二进制和相关资源。系统通过验证描述文件和证书链来确定App是否被授权在对应设备上运行。笔试里常问“企业证书和个人证书的区别”。这里必须强调只有企业开发者账号能创建In-House描述文件支持内部分发不需要上架App Store但该证书无法用于上架。个人开发者账号无法生成用于免上架分发的证书除非通过TestFlight做外部测试。TestFlight最多支持90天且受到名额限制。当年我用企业证书发布内部App时最怕证书到期。每次到期会导致所有企业包无法打开。后来我写了脚本在证书到期前30天自动发送钉钉提醒并维护两个证书交替使用降低断档风险。笔试题目如果问“证书过期了怎么排查”可以从以下三点回答查看安装失败日志解码描述文件里的ExpirationDate。通过security cms -D -i embedded.mobileprovision查看描述文件详细信息。系统日志中搜索provision关键字会看到“provisioning profile expired”之类描述。4.4 iOS 26的UIApplication.shared.setAlternateIconName系统确认弹框问题第三批笔试拿到iOS 26相关的题正好赶上系统版本新特性。题面问“调用setAlternateIconName切换App图标时系统会弹出确认框如果不想弹框直接切换应该怎么做”。实际上即使在iOS 26之前setAlternateIconName本身就会弹系统确认框这是防止恶意切换App图标的措施。iOS 26起苹果仍然保留这个确认框。而如果确认框弹出时机有误常见诱因是调用了beginBackgroundTask或CFRunLoopRun导致主线程卡住导致回调没有回来。针对这个需求最好的做法是在Info.plist中声明所有可切换的图标名称。调用前确保App处于活跃状态不要在applicationDidEnterBackground中触发。如果想实现无感切换可以在用户点击时先隐藏UI然后调用切换方法用预加载的方式确保图标资源已存在等系统确认框消失后设置过渡动画。5. 典型错误复盘OC/Runtime与架构设计避坑指南5.1 候选人常犯的Objective-C错误这批笔试批改下来很多错误非常一致把__weak与__block混用。在ARC下如果block内部需要修改外部变量必须使用__block如果只是防止循环引用用__weak。两者解决的问题不同可以同时存在。在dealloc中调用[super dealloc]。ARC环境禁止手动调用只有MRC才需要新增的面试者尤其容易混。认为weak指针指向的对象释放后指针会自动置空。这句话严格说只在ARC下成立而且对象内存必须支持弱引用如果是裸对象内存分配或C类型的包装不一定能自动置nil。5.2 架构设计题如何设计一个高可扩展的IM聊天模块这套笔试的压轴题是“假设你现在要开发一个IM模块支持文本、图片、语音、视频、系统通知等消息类型后续还会不断扩展。你会如何设计消息模型和UI展示层”。5.2.1 设计一个合理的数据模型正确的做法是采用“协议 基类”方式。定义一个MessageModel协议规定所有消息必须返回messageId、fromUser、timestamp、contentType等字段。具体消息类型各自对应一个类比如TextMessageModel、ImageMessageModel、VoiceMessageModel。使用协议的好处是以后新增一种消息类型不需要修改已有的列表逻辑只要新创建一个类并遵循协议注册到工厂中即可。如果使用一个庞大的基类每新增一种消息都会污染基类最终变成上帝类。面试时可以顺手补充一段伪代码protocol MessageModel { var messageId: String { get set } var timestamp: TimeInterval { get set } var contentType: MessageContentType { get } func cellIdentifier() - String }5.2.2 扩展性更优的UI层设计消息UI使用UITableView或UICollectionView实现。每个cell根据contentType返回不同的cell类型用复用ID区分。这里有一个关键点不要每次在cellForItemAt里判断上百种类型建议使用工厂模式生成cellViewModel。// 工厂模式 (MessageBaseCellViewModel *)viewModelWithModel:(MessageModel *)model;如果业务继续膨胀可以引入模板方法模式让每个子类cell负责自己的布局更新。这样新增消息类型时只修改工厂和新增cell即可。5.2.3 涉及存储和网络的扩展点如果IM还要支持离线消息需要设计本地数据库表。我通常用SQLite存储消息列表表结构至少包含localId自增主键messageId唯一索引from/to/roomIdcontentTypecontentData二进制status字段发送中/成功/失败/已读createTime发送消息时先写库再走WebSocket服务端成功后更新状态。如果集成第三方云服务比如融云、环信它们的SDK通常直接提供消息缓存本地只需做一层薄封装避免重复造轮子。5.3 内存问题定位从Leaks到Allocations笔试中有道实战题给了段代码要求找出内存泄漏。核心就是养成先运行Leaks工具再分析Allocations历史的习惯。Leaks适合定位循环引用和未释放对象Allocations适合定位大块内存增长。针对循环引用最常用的是在dealloc方法里打印日志- (void)dealloc { NSLog(% dealloc, NSStringFromClass(self.class)); }如果跳转页面返回后没有打印对应的dealloc说明对象仍然被持有接下来需要检查block、delegate、NSTimer等持有关系。6. 常见问题与排查技巧实录6.1 遇到“unrecognized selector sent to instance”怎么办这是iOS开发中最常见的崩溃类型之一。排障思路如下找到崩溃日志中的selector名称和class名称。检查是否在分类中实现了该方法但目标类没有实现。检查是否在子类中声明了属性但没有自动合成实例变量。检查消息是否发给了僵尸对象。开启Zombie Objects后详细堆栈能给出释放前的调用顺序。如果项目中大量使用字符串转方法名调用可以用NSSelectorFromString包装一层安全检测防止把不存在的selector传给系统。6.2 页面卡顿的定位与优化实践工作中遇到过很多“页面滑动掉帧”的案例。通用优化流程是打开Instruments的Core Animation调试观察FPS。主线程耗时操作通过Time Profiler定位。优先检查是否有大量离屏渲染例如对图片设置了太大的cornerRadius或在Cell上频繁使用shadowPath。将图片解码从主线程移到子线程使用CATiledLayer或YYAsyncLayer异步绘制。对列表数据源做增量刷新不要全量reloadData。6.3 关于iOS 17/18的兼容性适配问题如果目标用户涉及iOS 17以上有两点值得提前准备iOS 17对隐私权限进一步收紧特别是相册和定位。如果App没有合理使用系统弹窗审核会被拒。iOS 18起WebView与本地资源的交互增加沙盒限制必须正确配置WKWebView的loadFileURL并允许读取临时目录。7. 我的备考建议与项目管理反思7.1 准备周期和刷题方法我建议候选人把备考周期控制在三到四周。前两周以知识点扫盲和手写核心代码为主第三周开始集中做真实笔试模拟第四周专门整理错题尤其是Runtime和并发相关。具体执行每天手写3个API实现或场景代码比如自定义UIStackView的自适应布局、线程安全单例。每两天用Instruments对示例项目做一次内存和卡顿分析。参加社区模拟面试让同伴随机抽题考察表达能力。7.2 面试答题技巧笔试中遇到不会的题目不要空着。尽量写出自己的思路包括问题发生的可能原因。你打算如何定位。可能的解决方案和备选方案。考官更看重分析问题的框架而不是一次性答出完美答案。比如“为什么页面启动慢了”可以分阶段回答main函数之前做了什么、main函数之后到首帧渲染之间做了什么、网络请求是否有阻塞。7.3 总结经验从一份笔试看iOS工程师能力模型这轮笔试最核心的导向不是筛选记忆力而是工程化思考能力。候选人需要明白iOS开发早就过了“会用UIKit写页面”的阶段现在需要的是理解底层机制、掌握性能调优、能独立做架构决策的工程师。我强烈建议在工作中养成写技术复盘的习惯不管是崩溃排查、性能优化还是组件封装及时记录并把每个“踩过坑”的点沉淀成文档或小Demo。等到换工作面试时这些都是你最宝贵的素材。最后再分享一个我自己的小技巧笔试前没必要背大量API文档但一定要亲手实现几个核心机制。比如手动实现一个KVO替代方案、手写一个线程安全的图片缓存器。这些代码量不多但能把Runtime、内存管理和并发知识穿透成一条线。无论后台怎么出题你应对起来都会从容很多。
返回列表