距离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做读写锁,或者干脆把数据结构改成线程安全的OSAllocatedArray(Swift里)之类的类型。但笔试真正想看的,是你知不知道“并发不只是加锁”。在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 doesn't 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 浏览器唤起安装App:Universal 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真题,别只背答案,试着把每一道题当成一个小课题去研究。等你能把原理、代码和线上问题串成一个完整的故事时,通过笔试就只是顺带的结果了。