复盘:内存、Runtime、并发与系统适配全解析)
距离2020年B站校招已经过去几年了但那份iOS笔试卷二我到现在还记得很清楚。当时我正处在秋招海投的阶段B站的卷子在一众大厂里算不上最难但考察范围非常“实”——不堆冷门API也不靠脑筋急转弯而是把iOS开发日常中最容易忽略、又最能拉开差距的基础题揉在一起。做完之后我才意识到自己平时很多“能跑就行”的写法其实根本没吃透原理。所以这篇文章不是简单贴题目和答案而是把那份卷子里涉及的核心知识点拆开揉碎结合我后来在工作中踩过的坑讲讲每道题背后的思维逻辑。无论你是在准备iOS校招还是工作一两年想回头补基础这篇都能帮你理清复习方向。B站的题目风格其实代表了一类大厂的考察偏好不问你“用过什么”而是问你“为什么这么用”以及“出了问题怎么查”。1. 这份卷子考的不是API而是iOS工程师的基本盘说实话刚拿到卷子的时候我有点意外。没有一道题是问“XX方法的参数是什么”也没有“写出某个第三方库的API”。整张卷子扑面而来的感觉是它默认你已经会用UIKit、会写OC和Swift然后在这些基础上往深挖一层。这其实比直接考API更考验功底。1.1 从题目倒推B站iOS团队的考察偏好复盘这份卷子我能明显感觉到B站iOS团队想筛选什么样的人。首先他们对内存管理、Runtime、RunLoop这些偏底层的内容极其看重这类题目占比很高。原因也很简单B站App本身是一个重视频、重交互的复杂应用列表滚动、弹幕、播放器这些模块对性能和稳定性要求极高。如果一个人只会调系统API不知道内存是怎么分配的、对象是怎么生命周期管理的在遇到线上卡顿和闪退时会非常被动。其次工程化能力被放在了一个很隐蔽的位置。卷子里没有单独的大题来考“你怎么设计一个架构”但在很多选择题和简答题里都隐含了对代码组织、模块划分、依赖管理的考察。比如有一道关于单例与全局可变状态的题目表面看是在问内存线程安全实际上是在引导你思考“什么样的代码结构更容易出问题”。这也是B站这类业务复杂的App特别在意的点。最后是系统适配与真机调试。2020年那会儿iOS 13和iOS 14刚刚更替分屏、深色模式、权限变化都在陆续落地。卷子里出现了不少“在XX系统版本下会有什么表现”的题目这提醒我们iOS开发不能只盯着自己手头那个版本的模拟器而是要建立多版本、多机型兼容的意识。1.2 为什么“基础题”反而不容易答好这份卷子的难难在基础题的变体。举个例子它问“weak属性修饰的对象在dealloc时会不会自动置nil”这是基础吧但紧接着又问“在ARC下如果对象被多个线程同时持有weak指针是否一定安全”。如果你只背过“weak会自动置nil”没有理解weak底层是一张全局的哈希表、操作时需要加锁这道题就会做错。这类“基础题变体”恰恰是区分“背了两周面试题”和“真正写了一段时间iOS代码”的分水岭。我的感受是准备这类笔试不能只刷题而是要回到源码和文档里把每个机制的前因后果搞清楚。2. 内存、Runtime、并发这三块决定了你能不能过简历关我后来面试了不少iOS岗位发现大厂校招笔试基本都绕不开这三座大山。B站这份卷子二也不例外而且它的出题角度很能代表一线业务团队的真实需求。2.1 自动释放池与RunLoop的配合你真的理解了吗卷子里有一道关于自动释放池的题在主线程RunLoop的某个回调里创建了大量临时对象这些对象什么时候被释放很多人会答“出了作用域就释放”这其实是错的。在MRC时代autorelease对象会延迟释放在ARC下虽然编译器帮我们插入了release但autorelease机制依然存在尤其是系统框架返回的对象、或者用__autoreleasing修饰的对象它们会加入当前的自动释放池直到RunLoop进入休眠或池被销毁时才释放。这里要特别注意的是一层嵌套关系主线程RunLoop默认在每个事件循环开始前创建自动释放池结束后销毁所以一个RunLoop循环里积累的临时对象会在循环结束时统一释放。但如果某个回调特别长、又不断产生临时对象内存峰值就会异常高。我在实际项目中就遇到过这种问题一个列表页的cell高度计算里创建了大量NSDateFormatter这是个出了名的重对象列表快速滚动时内存一路飙高。后来改了复用策略并在计算密集的循环内部手动增加autoreleasepool块内存曲线才稳下来。2.2 Runtime和Method Swizzling笔试爱考线上慎用B站卷子二里有一道题是给一个已有类动态添加方法问你会不会以及添加之后的消息转发流程是怎样的。这题标准的答法是先走resolveClassMethod:或者resolveInstanceMethod:如果返回NO再走forwardingTargetForSelector:再不行就进入完整的methodSignatureForSelector:和forwardInvocation:。简单说Runtime给了一个“三次救场”的机会让你在对象收到无法响应的消息时还可以补救。Method Swizzling也是高频考点。笔试可能只考你会不会交换方法实现但实际工作中还得考虑几个关键问题第一交换操作要在load里做避免并发问题第二要保证方法交换只执行一次否则会交换来回相当于没交换甚至无限递归第三交换后的方法要记得调用原来的实现。我在做无痕埋点的时候就大批量用过Swizzling把viewWillAppear:、sendAction:to:from:forEvent:这些方法统一hook了一遍省了很多业务代码侵入。但如果项目里多人都在Swizzling同一个方法排查问题会非常酸爽所以一定要有统一的封装和日志。2.3 并发编程的坑笔试比面试更容易暴露并发题在这份卷子里占比不低其中一道经典题是多个线程同时读写同一个可变字典会发生什么其实就是表现不稳定可能crash、可能脏读、也可能一切正常。iOS端的并发问题不是靠“记结论”能解决的因为同一个问题在真机和模拟器上的表现都可能不一样这取决于线程调度的时机。正确的处理方式随手就能列出一堆用dispatch_barrier_async做多读单写、用os_unfair_lock保护临界区、用pthread_rwlock做读写锁或者干脆把数据结构改成线程安全的OSAllocatedArraySwift里之类的类型。但笔试真正想看的是你知不知道“并发不只是加锁”。在iOS开发里尽量把数据不可变、把操作收敛到一个串行队列往往比疯狂加锁更优雅。我记得当时在卷子上写了一句话能用队列解决的问题不要用锁。这个思路后来在工作中帮了我很多。3. 从iOS分屏到电池优化系统级适配题的答法2020年正值iPadOS分屏功能普及iOS 13/14又更新了一批系统行为所以B站这份卷子二里有大量“适配题”。这类题看起来像常识题实际上相当考验一个开发者平时有没有真机测试的习惯。3.1 分屏和窗口尺寸变化最容易翻车的布局题关于iOS分屏最常考的是App支持分屏后viewWillTransitionToSize:和viewDidLayoutSubviews会被怎么调用如果布局用了autoresizing或者frame写死分屏调整尺寸时就会出现按钮重叠、页面空白之类的问题。我自己的经验是只要项目最低支持版本在iOS 11以上就应该用Auto Layout SafeArea来做布局并且在涉及size class变化时重新计算那些动态高度。千万不要以为“用户不会在iPad上分屏用你的App”一旦用了分屏屏幕宽度可能从1024瞬间变成678那种即时变化对布局的考验比转屏还要大毕竟转屏还有个动画过程分屏更多是直接改变容器尺寸。另外分屏模式下UITraitCollection也会变化。比如横屏的regular宽度会变成compact如果你在代码里缓存了某个UITraitCollection相关的样式记得在traitCollectionDidChange:里刷新。这个坑我踩过表现就是分屏后字体突然变得异常大或异常小。3.2 蓝牙、后台定位与电池优化能力题背后的工程素养热词里有个“ios ble连接参数规范”这让我想起B站卷子里有一道关于蓝牙外设连接的题目CBCentralManager连接设备的参数里有哪些会影响功耗和连接稳定性。其实这里想考的是iOS在蓝牙上的系统限制和最佳实践。比如连接外设时CBConnectPeripheralOptions里有CBConnectPeripheralOptionNotifyOnConnectionKey如果你不处理这些后台通知选项App切到后台后连状态变化都感知不到。再比如扫描参数CBCentralManagerScanOptionAllowDuplicatesKey如果设为YES系统会频繁上报重复广播包非常耗电默认应该是NO。电池优化也是B站这类视频App很关注的点。卷子里问过“为什么在后台播放视频时会突然掉电变快”这其实涉及到AVPlayer的解码行为和后台运行模式。如果你在后台任务里做了大量CPU密集操作系统会很快把你的App挂起。合理的做法是尽量把拉流、解码等重活交给系统框架App自身只做必要的数据更新。我记得那题的答题落脚点是不要把系统能做的事自己做这是iOS开发的一个底层哲学。3.3 深色模式与权限变更容易被忽略的隐性适配除了分屏深色模式也出现在这套卷子里。它问的是你的App使用了一个固定背景色为白色的UIView在深色模式下会出现什么表面答案是“这块区域会突兀地显示白色”但背后的考点是你是否了解UIColor的dynamicProvider以及traitCollectionDidChange的触发机制。深色模式适配不是光给每个View设一个黑底就行关键是要让颜色“跟着系统走”。最简单的方式是用系统提供的语义色比如UIColor.labelColor、UIColor.secondarySystemBackgroundColor。如果是自定义主题色就要用init(dynamicProvider:)在light和dark两种模式下分别返回不同颜色。这里还有个坑是如果你在某个时机手动设置了overrideUserInterfaceStyle那么App内部的层级显示可能会和系统设置不一致测试时最容易发现“为什么我手机开深色App还是亮的”这种问题。权限变化也是那两年的高频考点。iOS 13之后UIAlertView以及相关的新API对定位权限的弹窗策略发生了变化区分“使用时”和“总是”两个级别。笔试里考到这类题本质是希望开发者在申请权限时提前规划好用户拒绝的情况给出友好的引导。4. 证书、打包、上架笔试卷里藏着的工程化考点这份卷子二里有一类题让我印象很深不直接写代码而是给你一段报错信息或一个发布流程让你判断问题出在哪儿。这其实是很多校招同学最头疼的部分因为学校课程几乎不教只有真实发过版的人才能答对。4.1 开发者证书与描述文件2020年已经是最常见的坑热词里有“ios开发者app证书更新”和“ios开发者模式”这确实是iOS开发的日常。我见过太多人在证书过期后一脸懵“昨天还好好的今天Xcode就报Provisioning profile doesnt include signing certificate。”笔试试卷里很喜欢考这类问题的排查链路。核心机制是这样的iOS App的签名体系由证书Certificate、描述文件Provisioning Profile和私钥三部分组成。证书用于标识开发者身份描述文件绑定了App ID、设备列表和权限私钥则是你本机用来签名的关键。如果换了电脑而没迁移私钥就算证书和描述文件都在Xcode依然会报ERR -67030或者找不到证书。这种问题在团队协作时尤其常见。我自己的做法是在钥匙串里把“我的证书”导出成.p12文件连同描述文件一起备份到团队共享的位置或者纳入内部文档库并标明过期时间。每次新同事入职先让他导入.p12再刷新描述文件基本就不会有签名问题了。4.2 uniapp打包iOS流程和原生iOS打包的区别热词里出现了“uniapp ios app打测试包全流程”和“uniapp打包ios流程”这说明跨平台开发也是当时笔试可能会延伸出来的话题。B站虽然主业是原生App但校招笔试偶尔也会用这类题来试探你的知识广度。uniapp这种跨平台框架打包iOS包时其实还是走Xcode那一套签名体系你需要在HBuilderX或CLI里生成iOS离线打包资源然后放到原生工程中用Xcode完成签名、导出归档、上传App Store Connect。这个过程里有几个点很容易踩坑一是证书类型要选对开发证书和发布证书不能混用二是Privacy - Camera Usage Description这类权限描述字符串必须配置不然调用相机或相册时会直接闪退三是capabilities里的推送、后台模式等配置要和你在开发者后台开通的权限一致。我当时帮朋友排查过一个uniapp的iOS包问题开发环境的包能装上一上TestFlight就显示“无法安装”。最后定位到是描述文件里没有包含测试设备的UDID原因是TestFlight的构建用的是一种特殊的“分发描述文件”它不依赖具体设备UDID但如果你的App ID配置或证书类型为Development就会在有TestFlight时出现奇怪的问题。解决方案就是上TestFlight一律使用Ad Hoc或App Store分发描述文件。4.3 从代码到上架笔试题其实在考你“有没有发布过App”试卷里有一道题是“iOS上架审核被拒提示2.1大礼包你会怎么处理”这题放到今天也不过时。2.1是App Store审核中比较让人头疼的条款通常意味着你的App在元数据、用户权限、内容合规等方面有问题审核团队给了一个笼统的拒绝理由。处理这类问题最重要的不是急于写邮件申诉而是自己先按审核指南逐条排查一遍把App里所有敏感点都列出来最好把登录流程、用户生成内容、版权声明这些都截图存档再通过App Store Connect的“申诉”通道说明你的情况和处理方案。B站卷子里考这种题其实是在筛选“有真实上架经验”的人。很多培训机构的项目都只是跑通模拟器从没走过TestFlight、也从来没被审核拒绝过遇到这类题就只能空泛地编理由。准备校招的时候我强烈建议你哪怕只是做一个功能简单的Demo也完整走一遍“开发-打包-上传-审核-上架”流程这套经验的价值远比多刷几个API题要高。5. 抓包、调试与WebView笔试之外的实战分水岭回到热词里的几个关键词“charles ios抓包”、“ios浏览器唤起安装app”、“uniapp ios webview 访问本地图片”。这些看起来不像笔试题但它们恰恰是iOS工程师日常调试和排查问题最常遇到的场景。B站这份笔试卷二虽然没有直接考这些工具的名字但里面的几道场景题背后全靠这些手段去验证。5.1 为什么Charles在iOS上抓不到包是证书还是代理问题Charles是macOS上最常用的抓包工具之一。很多人第一次用的时候都会遇到同一个问题手机设置了代理为什么还是看不到HTTPS的请求内容答案基本都指向一个地方——没有安装并信任Charles的SSL证书。在iOS上正确步骤是Mac端打开Charles在Proxy设置里开启SSL Proxying并添加需要抓包的域名*表示全部然后手机连接同网段WiFi把HTTP代理手动指向Mac的IP和端口默认8888接着用Safari访问chls.pro/ssl下载证书到“设置-通用-关于本机-证书信任设置”里把完全信任开关打开。这还没完iOS 10之后如果你想在App内抓HTTPS包光信任证书还不够还要在App的Info.plist里配置NSAppTransportSecurity的NSAllowsArbitraryLoads开发调试时或NSExceptionDomains指定域名否则系统的ATS策略会直接拦截。但这里有个很容易忽略的坑如果你的App开启了证书绑定SSL Pinning即使Charles装了证书也抓不到内容请求会报connection closed之类。这种情况只能通过hook网络库、或者用越狱环境下的抓包工具去绕但这不在常规笔试范围内。笔试里如果出现抓包题大概率只是想考你“证书信任”和“ATS”这两个点。5.2 浏览器唤起安装AppUniversal Link和Scheme的区别热词里“ios浏览器唤起安装app”也是一个经典场景。iOS支持通过Universal Link和自定义URL Scheme在浏览器里唤起App如果App没装则需要跳转App Store。最简单的方式是配置自定义URL Scheme比如bilibili://然后通过safari打开这个链接就会弹出“是否在Bilibili中打开”。但URL Scheme有个问题如果Scheme被其他App抢注或者用户没装对应App页面会报错。Universal Link则是苹果推荐的方式它通过HTTPS域名关联App在Safari里访问该域名下的特定路径时系统会直接唤起已安装的App没装就正常打开网页。笔试里如果考到这个往往还会追问“Universal Link在iOS 13之后有什么限制”。我记得的是从iOS 13开始Universal Link受到更多权限管理约束如果用户选择过“在浏览器中打开”系统可能会记住这个选择另外Associated Domains必须在开发者后台和App entitlements里同时配置缺一个都不行。这个功能我在实际项目里配置过踩过最典型的坑是服务器上的apple-app-site-association文件返回的Content-Type不对导致苹果永远无法正确读取调试了半天最后用curl -I一看发现服务器默认返回的是text/plain改成application/json就好了。5.3 WebView交互的边界本地图片、Cookie和缓存跨端场景里WKWebView是绕不开的。B站这种内容型App很多运营页都是H5承载的所以笔试里偶尔会出现“WKWebView的Cookie怎么同步”之类的题。热词里特别提到了“uniapp ios webview 访问本地图片”这其实是一个很具体的坑。在Native与H5交互时如果你想在WKWebView加载的页面里访问本地沙盒图片最简单的方式是用fileURLWithPath:加载本地HTML并通过loadFileURL:allowingReadAccessToURL:指定可读取的目录。如果只给HTML文件本身开权限H5里的相对路径访问图片资源就会失败必须把图片目录或者根目录一起授权。还有一个坑是如果你用loadHTMLString:baseURL:加载baseURL不能随便填一个URL否则图片和CSS都加载不出来。正确的做法是把baseURL指向App沙盒内的目录让H5可以通过相对路径去拿本地资源。另外WKWebView和Safari在Cookie策略上有些细微差别。iOS 11之前WKWebView的Cookie存得比较分散容易出现“登录态不一致”的问题。从iOS 11开始WKWebsiteDataStore.default()提供了统一的存储但如果你想在Native的网络层比如NSURLSession和WKWebView之间同步登录Cookie还得手动把HTTPCookieStorage里的Cookie塞给WKWebView或者反过来。我在做混合开发时最常用的方案是在WebView创建前先把原生登录接口拿到的Cookie写入WKWebsiteDataStore然后在WebView加载H5时H5直接就能读到登录态。6. 考后复盘我用这份卷子重构了自己的iOS学习路线做完B站这份笔试卷二之后我最大的感受不是“哪些题不会”而是发现自己之前的学习方式太碎片化了。今天看一篇Runtime教程明天写一个动画Demo知识点像散沙一样一到需要串联起来解决问题的场景就露馅。6.1 建立“三轮复习法”比盲目刷题更有效率第一轮是把iOS基础重新过一遍OC的底层、Swift的核心概念、内存管理、RunLoop、多线程、网络、存储这些都要求能用自己的话讲清楚原理并且能画出简单的流程图。这一轮不用做题但每个知识点都要找一个“为什么”比如“为什么NSTimer要用NSRunLoopCommonModes才能保证滑动不卡”这种级别的问题。第二轮是针对性地刷大厂校招笔试题但刷题不是目的而是要建立“题目-知识点-场景”的映射。比如看到“自动释放池”的题不只是背答案还要想一想“我在项目里哪里遇到过内存峰值异常”把这个真实案例和题目挂上钩。这样面试被追问的时候就不会哑口无言。第三轮是项目复盘。把自己做过的项目按模块列出来每个模块挑一个技术难点准备“背景-方案-结果-反思”四段式的描述。B站笔试的简答题其实都是这种模式它们不在乎你做过多牛的项目而在乎你有没有在项目里思考过技术选型。我当时写的最成功的一道题就是关于播放器缓存策略的优化因为那是真实踩过坑、改过代码、测过数据的回答起来有血有肉。6.2 笔试是起点不是终点回到标题里的“笔试卷二”其实它只是一个入口。真正通过笔试之后还有面试环节而面试官往往会拿着你笔试里答得不好的题继续深挖到源码级别。所以如果你现在也在准备校招我特别建议你用类似的方式复盘错题每道题不只是把正确答案记下来而是沿着这个知识点向下再问三个“为什么”直到你觉得自己没法再问下去为止。我在准备那年的秋招时把B站这份卷子折腾了整整一个多星期不是因为题量大而是每一道题都能牵出一长串相关知识点像查资料一样越查越深。最后虽然没去成B站但那份卷子帮我把iOS知识体系打通了后面再遇到其他厂的笔试题明显感觉轻松了不少。如果你手头也有这份卷子或者正准备刷大厂iOS真题别只背答案试着把每一道题当成一个小课题去研究。等你能把原理、代码和线上问题串成一个完整的故事时通过笔试就只是顺带的结果了。