作为一个在iOS开发圈子里混了快十年的老油条看到“顺丰科技2019秋招iOS开发工程师客观题合集”这个标题第一反应是亲切第二反应是感慨。亲切的是这套题基本代表了当年物流科技巨头对iOS开发者基础能力的要求基准感慨的是几年过去题面可能会变但底层考察的知识模型放到今天依然是面试准备的核心骨架。这篇文章不是简单把题念一遍而是想借着这套题聊聊它背后到底在考什么、为什么这么考以及围绕这些考点我们实际开发中应该怎么沉淀。1. 这套题背后的考察逻辑顺丰科技到底想要什么样的iOS开发1.1 从物流场景反推技术画像很多人看到“顺丰科技”四个字第一反应是做物流App。实际上顺丰科技的业务盘子很宽丰巢、顺丰同城、供应链中台、大数据平台都在做。但无论哪个业务线iOS端的基础能力要求是共通的稳定、高效、对细节敏感。这套客观题不考前沿的SwiftUI不考Metal性能优化不考跨端框架的底层原理而是老老实实地考OC的底层实现、内存管理、多线程、网络、RunLoop、数据持久化这一套。这说明什么说明大厂基础岗位的筛选逻辑是统一的——先确认你具备扎实的客户端基本功再谈业务匹配度。物流场景中轨迹推送的可靠性、大图上传的稳定性、扫描枪蓝牙配对的兼容性哪一个不是从基础能力长出来的1.2 客观题的筛选效率与考察边界客观题的优点是筛选效率高、标准统一缺点是没有办法考察设计能力和排查思路。所以这类题往往有两个极端一部分是“背了就会”的概念题一部分是“看着都会、一选就错”的陷阱题。后者才是拉开差距的地方它考察的是你在实际开发中撞过多少坑以及你是否真正理解底层机制而不是只停留在API调用层面。我常跟新人说如果你把一套校招客观题里所有选项都搞明白为什么对、为什么错并且能现场写出小demo验证你的判断那你的基础能力基本就过关了。这套顺丰科技的题合集完全适用这个练习方法。2. 基础语法与内存管理最容易翻车的“送分题”区域2.1 属性修饰符不只是背atomic和nonatomic几乎每一套iOS笔试题都会考属性修饰符顺丰这套大概率也不例外。但你真的理解它们之间的关系吗property (nonatomic, readwrite, copy) NSString *name; property (atomic, strong) NSMutableArray *items;第一个问题copy和strong的区别到底是什么很多人能背出“copy会拷贝一份strong是引用”但放到具体场景里就糊涂了。比如name如果用strong修饰外面传一个NSMutableString进来之后外部修改了字符串内容你的self.name也会跟着变。这在用户信息展示场景下就是潜在的数据污染 bug。用copy就能保证属性持有的是赋值那一刻的快照。第二个问题atomic真的线程安全吗答案是“不”。atomic只能保证属性的 getter/setter 操作是原子的但不能保证你对这个对象的读写组合是原子的。比如self.items addObject:]这一整个操作即使items是atomic多线程同时调用依然可能出问题。正确做法是加锁或者用串行队列做同步。2.2 MRC到ARC的思维转变内存管理的本质现在很多新入行的开发者直接就是ARC时代完全没有手动管理内存的经历。但客观题偏偏喜欢考MRC下的内存管理逻辑因为这是理解ARC底层行为的基础。ARC本质上编译期帮你在合适的位置插入了retain/release/autorelease。你需要理解的是谁持有谁释放所有权转移的规则autorelease pool的作用延迟释放用于方法返回值循环引用为什么需要weak对象之间互相强引用导致无法释放我建议准备这类题目时不要只背结论而是在Xcode里关掉ARC手动写一个类的内存管理代码感受一下编译器到底替你做了什么。这一步的体感价值远大于刷一百道概念题。2.3 Block的内存管理从栈到堆的迁移Block也是笔试高频考点。很多新人只知道block要用copy修饰但不知道为什么。这里有个关键的历史背景早期的block可能分配在栈上栈上的block在函数返回后就失效了所以需要copy到堆上。而ARC下编译器会自动处理大多数情况所以现在你用strong修饰block一般也没问题但规范写法依然是copy。更深一层的考点是循环引用block捕获selfself又持有block形成保留环。解决办法是__weak typeof(self) weakSelf self;。但笔试不会止步于此高级一点的问法是如果block内部有延迟执行使用__strong重新声明一次 weakSelf 是为了什么答案是防止执行过程中self被提前释放保证block生命周期内的安全。3. 多线程与并发从GCD到锁考察的不只是API3.1 队列与任务串行、并行、同步、异步的排列组合多线程是iOS面试的重头戏顺丰这套题大概率会涉及GCD的核心概念。很多新人分不清“串行队列”和“同步任务”的区别这里有一个直观的理解方式队列决定了任务的执行顺序串行还是并行任务本身决定了是否阻塞当前线程同步还是异步举个例子dispatch_queue_t serialQueue dispatch_queue_create(com.example.serial, DISPATCH_QUEUE_SERIAL); dispatch_async(serialQueue, ^{ NSLog(任务1); }); dispatch_async(serialQueue, ^{ NSLog(任务2); });这段代码能保证任务1先于任务2执行因为是在串行队列上异步提交。但如果是并行队列两个任务的执行顺序就不确定。而如果在主队列上执行同步任务就会死锁因为主线程正在等待这个同步任务执行完而这个任务又被排在主队列的末尾永远等不到执行。3.2 死锁的经典场景主队列同步任务主队列同步任务死锁是笔试必考内容。你可以用下面这段代码测试- (void)viewDidLoad { [super viewDidLoad]; dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(不会执行到这里); }); }运行后App直接卡死。原因是viewDidLoad本身在主线程执行dispatch_sync将block提交到主队列主队列是串行的新的block必须等当前任务viewDidLoad执行完才能开始而当前任务又在等这个block执行完互相等待形成死锁。3.3 锁的选择从OSSpinLock到os_unfair_lock笔试中有时候会考到锁的选型这题非常能区分“背题”和“真正开发过”。早期很多资料推荐OSSpinLock但它在优先级反转场景下有严重问题低优先级线程持有锁时被高优先级线程抢占高优先级线程自旋等待锁导致低优先级线程一直得不到CPU形成死锁。所以苹果后来废弃了它推出了os_unfair_lock。实际开发中我个人的选择标准是锁类型适用场景注意点os_unfair_lock短临界区、高频加锁不可递归不可在block内长期持有NSLock通用互斥锁用起来简单性能中等NSRecursiveLock递归调用中需要加锁避免死锁但开销比NSLock大dispatch_semaphore控制并发数量、同步多任务信号量初始值要设计好synchronized简单场景、可递归性能最差但不易出错我见过不少同学把synchronized用在性能敏感路径上这就是典型的不了解底层表现。遇到高频调用的场景请务必用os_unfair_lock或者原子操作代替。4. 网络层与数据持久化客户端开发的核心能力盘4.1 HTTP与HTTPS面试必考的非对称与对称加密关于HTTP和HTTPS几乎每一套iOS笔试题都有。但很多人的理解停留在“HTTPS就是加密的HTTP”这一层。真正要过关你得能把整个握手过程讲清楚客户端发起HTTPS请求服务器返回数字证书客户端验证证书的合法性通过CA公钥验证签名验证通过后客户端生成一个随机密钥对称加密密钥用服务器的公钥加密后发送给服务器服务器用私钥解密得到随机密钥之后双方用这个随机密钥进行对称加密通信这里最核心的考点是为什么握手阶段用非对称加密传输阶段用对称加密答案是性能。非对称加密计算开销大不适合大量数据对称加密速度快但密钥分发有风险。所以两者结合取长补短。4.2 NSURLSession的底层机制与连接复用到了代码层面的考题NSURLSession基本是跑不掉的。一个很容易被忽略的点是连接复用。NSURLSession默认使用HTTP/1.1的keep-alive机制同一域名下的请求会复用TCP连接。但如果你的请求头里带了Connection: close连接就会被关闭下一个请求需要重新建立TCP连接增加延迟。另外NSURLSessionConfiguration里的timeoutIntervalForRequest和timeoutIntervalForResource是两个完全不同的概念。前者是请求超时后者是资源加载的总超时。很多开发者只设置了前者结果大文件下载场景下后者默认值可能导致意外超时中断。4.3 数据持久化选型从NSUserDefaults到SQLite顺丰这套题中数据持久化大概率会涉及以下知识点的对比NSUserDefaults适合轻量级配置存储内部是plist文件读写是全量刷新。不适合存大数据频繁写入会导致性能问题。Keychain适合存敏感信息如token、密码。系统级加密卸载App后数据依然存在这是考点。文件存储适合存图片、音频、Cache等。注意区分Library/Caches和Library/Application Support前者可能被系统清理。SQLite适合结构化数据、需要查询的场景。直接使用需要写SQLFMDB已经停止维护了现在更推荐使用GRDB或者WCDB。面试中被问到“你们项目里为什么用SQLite而不用CoreData”这是一个很深的话题。CoreData的模型迁移、并发处理复杂度高团队成本大。SQLite简单直接配合FMDB/GRDB也能很好地完成数据层工作。重要的是你要说清楚选择的理由而不是只说“SQLite更轻量”。5. 界面渲染与响应链iOS开发者的内功心法5.1 RunLoop与界面更新为什么setNeedsLayout不会立即布局RunLoop是笔试中的高难度知识点它的核心价值在于理解iOS的事件循环机制。什么是RunLoop它是线程内部的事件循环作用是让线程在有任务时工作没有任务时休眠节省CPU资源。RunLoop可以监听的事件源包括Timer、Source触摸事件、performSelector等、Observer监听RunLoop状态变化。面试常考的一个陷阱是setNeedsLayout为什么不会立即触发布局因为setNeedsLayout只是打了个标记真正的布局会在当前RunLoop周期的某个时间点统一处理。这其实是一个性能优化设计——避免一个RunLoop周期内多次修改frame导致重复布局计算。这个知识点和UITableView的滑动卡顿优化关系密切。理解了RunLoop你就知道为什么drawRect:不能经常被调用为什么CADisplayLink在滑动时可以提供60FPS的刷新频率。5.2 响应链与事件传递hitTest的完整逻辑触摸事件在iOS中的传递机制是另一个高频考点。它分为两个阶段第一阶段从UIWindow向子视图递进查找找到最合适的view- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (!self.isUserInteractionEnabled || self.isHidden || self.alpha 0.01) { return nil; } if (![self pointInside:point withEvent:event]) { return nil; } for (UIView *subview in [self.subviews reverseObjectEnumerator]) { CGPoint convertedPoint [subview convertPoint:point fromView:self]; UIView *hitView [subview hitTest:convertedPoint withEvent:event]; if (hitView) { return hitView; } } return self; }这段代码描述了hitTest的默认逻辑你会看到它先判断自身是否可交互、是否隐藏再判断点击点是否在自身范围内然后逆序遍历子视图查找。这个过程中只要有一层返回nil事件就不会传递到更深层。第二阶段沿着响应链向上传递直到找到能处理事件的响应者如果hitTest找到了目标view但target不处理这个事件事件就沿着响应链nextResponder向上传递——从view到superview到viewController再到window最后到AppDelegate。这个机制解释了为什么很多自定义view能监听其他控件的事件。这个知识点特别实用。比如你想让一个UIButton扩大点击区域而不改变视觉大小就可以重写hitTest在按钮的superview里做点击区域判断。6. 架构设计与代码质量从“能写”到“会写”的分水岭6.1 MVC的缺陷与MVVM的改进校招笔试中出现架构设计题通常不会要求你写完整的架构而是考察你对MVC和MVVM的理解深度。MVC在iOS中的经典问题是ViewController过于庞大。它既负责View的生命周期管理又承担ViewModel的业务逻辑甚至还有网络请求的Delegate。这个问题在大型项目中会被放大到难以维护的程度。MVVM的核心思路是把ViewController中的业务逻辑迁移到ViewModelView只负责展示和交互事件的上报ViewModel负责处理业务逻辑并产生可展示的数据。通过KVO、Block、Delegate或ReactiveCocoa/RxSwift进行数据和视图的绑定。举个例子一个登录页面。// ViewModel interface LoginViewModel : NSObject property (nonatomic, copy) NSString *username; property (nonatomic, copy) NSString *password; property (nonatomic, copy) void (^loginSuccess)(void); property (nonatomic, copy) void (^loginFailure)(NSError *error); - (void)login; end implementation LoginViewModel - (BOOL)isValidInput { return self.username.length 0 self.password.length 0; } - (void)login { if (![self isValidInput]) { self.loginFailure([NSError errorWithDomain:LoginError code:400 userInfo:{NSLocalizedDescriptionKey: 用户名或密码不能为空}]); return; } // network request here } end这样ViewController只需要接收用户输入把数据传给ViewModel再监听登录回调即可。ViewController的代码量能减少40%以上。6.2 组件化与路由大型项目的必然选择大厂笔试的最后一部分往往会涉及组件化的思考。顺丰科技这种规模的公司App通常有几十上百个业务模块如果都用import依赖的方式互相引用编译时间、模块解耦、团队并行开发都会出问题。组件化的核心是每个业务模块是一个独立的仓库或独立的target通过路由中间件进行通信。常见的路由方案有URLRoute如MGJRouter、Target-Action如CTMediator和Protocol-Class方案。URLRoute的优势是统一入口可以配合WebView进行统一跳转Target-Action的优势是编译期安全不依赖运行时Protocol-Class则是把接口和实现完全解耦。选用哪种方案取决于团队规模、模块粒度、以及是否需要支持远程动态跳转。我个人在实际项目中使用的是Protocol-Class方案理由是编译期安全性和代码可读性都比URLRoute好且不需要维护路由表。7. 针对这套题的复习策略与实战方法7.1 以题目为索引建立知识图谱很多同学刷题的方式是“对答案、背答案”这是效率最低的方式。我的建议是以一道题为索引向外延伸出十个相关知识点。举个例子题目如果考“下列哪个关键字可以将block从栈拷贝到堆”正确答案是copy。但你不要停在答案上继续问自己copy和strong修饰block有什么本质区别ARC下block什么时候还会在栈上block捕获外部变量时什么情况需要__block修饰block作为属性为什么用copy而不是assign__weak在block中的作用是什么什么时候会产生循环引用block底层会生成什么样的C结构体每个问题都是一道新的面试题。这就是知识图谱化复习法。7.2 用Xcode验证每一个不确定的选项准备这套笔试题最好的工具不是题库而是Xcode。每当你对一道题的某个选项不确定就写一个小demo验证它。比如你不确定NSMutableArray的copy是深拷贝还是浅拷贝就写代码测试NSMutableArray *array [NSMutableArray arrayWithObjects:1, 2, nil]; NSArray *copyArray [array copy]; NSMutableArray *mutableCopyArray [array mutableCopy];运行后你会发现[array copy]返回的是不可变的NSArray[array mutableCopy]返回的是NSMutableArray。但这只是容器本身的拷贝容器内的元素对象指向的是同一份内存地址即浅拷贝。如果数组内存的是自定义对象修改对象属性两个数组都会受影响。这种验证方式比死记硬背“copy是浅拷贝还是深拷贝”要牢固得多。7.3 时间分配与心态调整面对一套完整的校招客观题时间分配很重要。我的建议是前20%的时间快速浏览全部题目标记出有把握的题中间60%的时间按顺序答题遇到不确定的题目不纠结先跳过去最后20%的时间回头解决跳过的题优先排除明显错误的选项心态上要清楚一件事客观题的价值不在于你答对了多少道而在于通过这套题暴露了多少知识盲区。有些同学答完题看到正确率低就崩溃完全没有必要。换个角度想现在暴露问题比入职之后在线上环境暴露问题要划算得多。7.4 从笔试到面试把客观题变成深度话题这套题的隐藏价值是客观题里出现的每个知识点都可以变成面试官追问的话题。比如客观题考了你“死锁的定义”面试官就可能追问“你在实际开发中遇到过死锁吗怎么排查的”如果你能说一次真实的排查经历——比如通过主线程同步任务导致的卡死、用Instruments的Time Profiler定位到卡顿线程、最终通过异步化重构解决——这就比任何标准答案都有说服力。所以我建议刷完这套题之后从每个模块挑出两三个你最熟悉的知识点准备成2-3分钟的深度阐述。这比死记硬背自我介绍有效得多。8. 一个过来人的心得基础能力是技术路上的底盘刷完顺丰这套题再回头看自己这些年的技术经历最大的感悟是框架和语言会更新换代但底层能力永远不会过时。2019年的这套题里考的是OC、RunLoop、GCD、内存管理。今天你去面试很多公司考的还是这些只不过加了Swift和SwiftUI的内容。为什么因为底层机制决定了上层应用的稳定性和性能。一个开发者如果对内存管理没有深刻理解哪怕用Swift写代码再快也会在不经意间制造出难排查的内存泄漏。所以我给所有准备校招的同学一个非常实在的建议不要追求刷题数量而是追求把每个知识点吃透。每做一道题就写demo验证每验证一个知识点就想一下它在你未来的项目里能解决什么问题。这样积累下来即使你没有进入顺丰科技这套题本身也会成为你技术成长路上非常扎实的一步。