ARTICLE DETAIL

资讯详情

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

网易iOS实习生笔试题复盘:内存管理、Runtime与多线程核心考点

网易iOS实习生笔试题复盘:内存管理、Runtime与多线程核心考点 网易2018实习生招聘的iOS开发笔试题过去这么多年回头看依然是很有代表性的“大厂实习生筛选卷”。它不像社招那样深挖源码细节也不像校招正式批那样动辄四道算法大题压轴。它的定位很刁钻在短短一两个小时里用一套覆盖面极广的选择题加简答题快速判断一个人有没有扎实的iOS基础、有没有阅读底层原理的习惯、有没有基本的工程思维。今天把这套题里最有代表性的几个考察方向拆开揉碎聊一聊顺便把解题背后的原理和备考逻辑一起讲清楚。1. 网易iOS实习生笔试的考察逻辑从招聘画像反推题目设计1.1 2018年校招背景下网易iOS团队想要什么样的人2018年正值移动互联网竞争白热化的阶段网易旗下产品线新闻、云音乐、严选、游戏周边等对iOS开发者的需求很旺盛。但实习生岗位和社招不同招进来的应届生大概率不是立刻上手写业务而是要经过mentor带教、参与模块开发、承担一些基础组件维护。所以笔试的底层诉求非常清晰基础要牢学过的东西不能只停留在用过层面思维要对遇到问题要知道往哪个方向排查代码要能写哪怕算法不强也不能连基本的链表反转都写不出来。这就决定了题目的难度曲线不是平均的。选择题覆盖面很广但基本都是你只要认真看过文档、写过几个Demo就不会错的级别简答题则明显在筛选看过源码和只背过面试题的人——这两类人面对同一个问题写出来的答案深度完全不一样。1.2 题型分布与考察维度总览从复盘的角度看这套笔试大致可以分成四个模块题型考察方向典型知识点举例难度定位选择题基础广度内存管理、多线程、RunLoop、网络协议、C语言中低考是否知道简答题理解深度Runtime消息机制、循环引用成因、UI性能优化中高考是否想清楚编程题算法与编码链表操作、字符串处理、栈与队列中考能否写对开放题工程思维设计图片缓存、实现某个功能模块中高考工程落地能力这个布局其实很聪明。选择题用来快速筛掉基础不牢的人简答题用来观察候选人平时有没有深入思考的习惯编程题用来验证动手能力开放题则是给那些基础扎实、有项目经验的候选人一个展示加分项的机会。四类题型环环相扣基本覆盖了一名合格iOS开发实习生的全部能力要求。1.3 网易这套题和腾讯、阿里的差异点在哪对比当年腾讯的“纯海量选择题两道编程题”和阿里“强算法强设计”的风格网易的笔试有个很明显的偏好它非常看重iOS原生技术栈的底层基本功。其他大厂可能50%的精力在考通用计算机基础网易这套题里iOS专项内容占比明显更高runtime、RunLoop、内存管理这类题出得相当细。这说明网易当时的iOS团队对候选人的期望是来了就能在系统底层方向上帮上忙而不是只做页面堆叠。2. iOS专项题内存管理、Runtime与多线程是绝对主线2.1 内存管理引用计数、weak与循环引用的“连环考”这套笔试里内存管理相关的题目基本是必考的而且考法非常典型。比如选择题会给一小段代码问最终打印出来的count是多少简答题会直接问“weak修饰的属性在对象释放后会发生什么为什么”。这些题目单独拎出来都不难但组合在一起考察的就是你有没有真的理解iOS内存管理的底层机制。先说引用计数这块。OC对象的生命周期靠引用计数管理retain加一、release减一、dealloc时清零。ARC只是在编译期自动帮你插入了retain/release代码但核心机制没有变。一个容易被忽略的点是在arc环境下函数参数、局部变量、属性赋值分别对应什么样的内存管理语义。很多人知道strong和weak的区别但笔试考的往往更细比如__strong修饰的局部变量在作用域结束时释放__autoreleasing则会延迟到autoreleasepool drain时才释放。这里有一个当年很多人丢分的点autoreleasepool的释放时机。选择题里常考“以下关于autoreleasepool说法正确的是”错误选项往往写“自动释放池在每次RunLoop循环结束时都会被释放”。这个表述其实不严谨。iOS在主线程的RunLoop里默认创建了一个自动释放池在RunLoop即将休眠时drain但如果一个RunLoop循环内连续执行了很多不需要休眠的任务就会在同一个循环内叠加大量autorelease对象导致内存峰值。这也是为什么嵌套循环里需要手动加autoreleasepool的原因。再来看weak的实现原理。很多背过八股文的人会写“weak不增加引用计数对象释放后自动置为nil”但简答题这样答是拿不到高分的。笔试真正想看到的是这个链路objc_initWeak调用通过objc_storeWeak把weak指针注册到对象的SideTable里的weak_table_t中对象释放时dealloc会走到objc_clear_deallocating根据对象的地址找到对应的weak_entry_t遍历entry里的referrers指针数组把所有weak指针都置为nil最后把这个entry移除。能写出这个流程说明你不仅知道weak的功能还看过runtime源码。这正好是网易这类笔试想筛选出来的人。类似的循环引用的简答题也爱考block、delegate、NSTimer这三类循环引用场景以及各自的解决方案。回答时要把delegate为什么要用weak因为持有方和被持有方生命周期通常不一致、NSTimer为什么需要中间层转发因为target被timer强持有都说清楚。2.2 Runtime消息机制从objc_msgSend到Method SwizzlingRuntime在2018年的网易笔试里几乎是必考大项而且考得比大多数刷题网站上的面试题更实在。选择题常考objc_msgSend的基本流程简答题会直接让考生描述“iOS中方法调用的完整过程”或者给出一个isMemberOfClass和isKindOfClass的辨析场景。先说说消息发送机制。一个方法调用[obj foo]在编译后会变成objc_msgSend(obj, selector(foo))它的查找流程可以分成四层在obj的isa指针指向的类对象的cache_t哈希缓存里查找方法找到就直接调用IMP缓存没命中就在该类的方法列表method_array_t里二分查找或者线性查找本类没有就通过superclass指针沿继承链逐级往上查找最终没找到进入动态方法解析resolveInstanceMethod:这里可以动态给类添加方法如果返回NO进入消息转发机制依次走forwardingTargetForSelector:和methodSignatureForSelector:/forwardInvocation:。当年这道题很多人只答了前三步丢掉了动态方法解析和消息转发。但实际上网易的笔试非常喜欢考“消息转发”这个点它和Method Swizzling一样是iOS面试里最能体现理解深度的内容。Method Swizzling本身也是简答题的高频题。考察点通常不在“怎么用”而在“为什么”。你要回答清楚Swizzling的本质是交换两个方法的IMP指针load方法中执行是因为load是线程安全的且只会调用一次交换后调用原来的实现需要用class_getMethodImplementation拿到的原始IMP避免死循环交换后如果父类和子类同时Swizzle同一个方法容易互相覆盖所以一般要做dispatch_once保护。有一个当年笔试中出现的变体题很有意思让考生判断“在分类里重写一个系统方法会不会影响其他类的行为”。这题考的是category的实现原理——分类的方法会被添加到类的方法列表的前面导致分类方法优先于原类方法被调用但这个行为是全局的不是实例级的所以会影响所有调用该方法的实例。很多人以为分类和子类重写的语义是一样的直接掉坑。2.3 多线程方案对比GCD、NSOperation、锁与数据安全多线程这块也是笔试的重头戏。网易的题目一般会从三个角度出概念辨析题、死锁分析题、线程安全题。概念辨析题最典型的是同步和异步、串行和并发之间是什么关系。很多人一看到“串行队列”就以为同步看到“并发队列”就以为异步这是大错特错。同步和异步描述的是“是否阻塞当前线程等待执行完”串行和并发描述的是“队列里的任务按什么方式执行”它们彼此独立。同步加串行队列不会新建线程只是在当前线程一个个顺序执行异步加并发队列才会根据系统空闲情况创建多个线程并发执行。这个组合一定要想透。死锁分析题几乎逢考必有。最经典的场景是dispatch_sync(dispatch_get_main_queue(), ^{ // 执行一些UI操作 });这段代码在任意非主线程的线程里调用如果在主线程里调用就会死锁。原因是主线程正在执行dispatch_sync这个调用sync方法会阻塞当前线程并等待block执行完毕而block又被提交到主线程的队列里等待执行但主线程已经被阻塞住了无法去执行新任务于是互相等待死锁。解答这类题的关键思路是死锁的本质是“提交任务的线程”和“执行任务的线程”是同一个而且这个线程正在等任务完成。顺着这个思路还能推断出在主线程里dispatch_sync到任意一个串行队列都会死锁不对如果目标队列不是主队列即使提交线程是主线程只要该串行队列空闲也只是把任务安排在另一个线程上执行主线程阻塞等待不会死锁。真正会死锁的组合只有“在当前正在执行的串行队列上同步提交任务到同一个队列”这一种情况。线程安全题喜欢出“以下哪种方式能保证线程安全”或者“这段代码存在什么问题”。这里有一个比较深的坑是atomic并不能保证对象操作的线程安全。atomic只能保证属性的getter/setter内部加锁但如果你先读取了一个可变数组然后往里面添加元素这个“读改”的组合操作并不是原子的。网易笔试里给过一个场景多个线程同时调用某个属性的addObject方法问为什么用atomic修饰属性还是崩溃了。答案就是atomic不保护“非原子操作”组成的复合操作正确做法是给操作加锁或者使用串行队列。3. 底层基础题C语言、RunLoop与网络协议3.1 C语言与指针实习生笔试的“筛选器”虽然顶着iOS开发实习生的名头但网易笔试从来不会放过C语言基础的考察。原因很直接iOS底层是C/C写的runtime、GCD、block这些机制不懂C语言根本没法真正理解。而且对于实习生C语言基础好不好基本能判断出你在学校有没有认真上过课。选择题里C语言的考察点集中在这几个方向指针运算、数组越界、字符串处理、结构体内存对齐。指针运算这道题当年出现的频率极高int a[5]; int *p a;问p1和a1分别指向哪里。答案是p1指向a[1]因为p是int*类型步长是4个字节而a1指向整个数组末尾之后的位置因为a的类型是int(*)[5]步长是整个数组的大小20个字节。这题考的是指针的类型语义而不是简单的一句“指针就是地址”。结构体内存对齐也是必考。比如struct Test { char a; int b; char c; };问sizeof(struct Test)是多少。很多人脱口而出6实际答案是12。因为编译器按默认对齐规则一般是4字节对齐会在a后面填充3个字节让b落在4字节对齐的地址上c后面再填充3个字节让整个结构体的大小是对齐值的整数倍。这个知识点在iOS里还有现实对应场景CoreFoundation、CGRect等底层结构体大量使用对齐来描述内存布局理解对齐规则对阅读底层源码很有帮助。字符串处理函数也是选择题的热门比如strlen和sizeof的区别——前者在运行时计算字符串长度不包含结尾的\0后者在编译期计算所占内存大小包含\0。简单但能筛掉一批根本没仔细写过C的人。3.2 RunLoop与UI性能面试官想听的“源码级”回答RunLoop是网易iOS笔试里很有辨识度的考点。大部分学校课程不会讲这个东西培训班也不一定会系统讲所以它成了筛选“真正对iOS底层有兴趣的人”的一道分水岭。选择题常考一个经典场景在UIScrollView拖动过程中NSTimer不触发了问为什么。答案核心是NSTimer默认被添加到RunLoop的kCFRunLoopDefaultMode下滑动时RunLoop切换到了UITrackingRunLoopMode默认模式下注册的timer不会被执行。但简答题不会只让你答到这个层面。笔试更想看到这样的完整链路RunLoop本质是一个mach_msg消息驱动的循环每一次循环会处理事件源Source、定时器Timer、观察者ObserverRunLoop有若干种mode每个mode包含一组source、timer、observer切换mode时当前mode下的所有item都不执行主线程的RunLoop默认运行在kCFRunLoopDefaultMode滑动过程中切换到UITrackingRunLoopMode所以DefaultMode下的timer和回调都暂停解决办法是把timer加入NSRunLoopCommonModes这是一个伪mode集合标记了它之后可以在default和tracking两种模式下都执行更深层的办法是评估timer本身的设计——如果只是做倒计时是否需要用CADisplayLink或者后台线程加dispatch_source_t。如果能写出第5点说明你不仅知道这个问题的“标准答案”还考虑到了实际工程的取舍这正是网易笔试简答题想要的高分答案。除了TimerRunLoop常和“卡顿”一起考。问到如何监控主线程卡顿时很多人会说用CADisplayLink或者子线程定时检测主线程是否响应。但笔试希望听到的是利用RunLoopObserver监听kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting两个状态之间的耗时当耗时超过阈值比如100ms就记录当前主线程的调用栈并上报。这个方案背后是主线程的事件处理都集中在RunLoop的source/timer回调里一旦卡顿RunLoop的循环就会被阻塞。理解这条链路才能答出“在哪个回调时机检测”而不是用笨办法轮询。3.3 网络协议栈HTTP、HTTPS与TCP的核心考点网络协议不是iOS独有的知识但在移动开发笔试里占比一直不低。网易的题喜欢结合移动端场景来出比如断网重连、弱网优化、HTTP缓存等而不是单纯背诵课本。选择题高频考点是HTTP状态码。2018年这套题里出现过让你判断以下状态码含义的题200、302、304、403、500。这里有个容易混淆的点是304 Not Modified——它代表资源未修改可以继续使用本地缓存这在移动端网络请求优化中非常重要。面试追问时往往会问“如果服务端返回304你需要做什么”。答案是不需要重新下载资源直接使用本地缓存同时更新本地缓存的有效期。HTTPS握手过程也是简答题热区。这道题考察的是候选人有没有真正理解“非对称加密交换密钥、对称加密传输数据”的完整链路客户端发起请求携带客户端支持的TLS版本、加密套件列表、随机数服务端返回证书、选定的加密套件、服务端随机数客户端验证证书合法性信任链、有效期、域名匹配验证通过后客户端生成预主密钥用服务端公钥加密发送给服务端双方用三个随机数生成会话密钥后面都用对称加密通信。笔试考这个除了看你对协议的掌握还有一层用意iOS中很多网络问题排查都涉及HTTPS抓包理解握手过程才能明白为什么中间人代理抓包需要先安装并信任证书也才能理解ATSApp Transport Security为什么要求服务器必须支持TLS 1.2以上。TCP三次握手和四次挥手也是老牌选择题。这里有个移动端相关的补充考点为什么连接关闭要四次挥手而不是三次。答案是TCP是全双工通道两个方向的数据传输是独立关闭的——A不再发送数据时可以发送FIN终止A到B方向的连接但B到A方向还可以继续传数据直到B确认数据传完才发送FIN因此比握手多了一次。这个点如果只背了“三次握手四次挥手”这个口诀很容易在变体题上翻车。4. 编程题与开放题算法功力与工程思维的较量4.1 笔试编程题的高频题型与解题模板网易这套笔试的编程题难度在中档偏简单基本是LeetCode中等偏下题目的水平但因为是在线笔试环境很多人会由于不熟悉输入输出写法、边界条件考虑不全而写挂。高频题型我盘了一下基本上是这几类链表反转、链表中环的检测、两个链表的交点字符串反转、字符串中第一个只出现一次的字符、最长公共前缀用两个栈实现队列、合法括号序列判断二叉树的前中后序遍历、层次遍历、最大深度。链表反转这道题算是最经典的但很多人一紧张就写不出来。这里给一个当年考场上很实用的写法——迭代三指针法struct ListNode* reverseList(struct ListNode* head) { struct ListNode* prev NULL; struct ListNode* curr head; while (curr ! NULL) { struct ListNode* nextTemp curr-next; curr-next prev; prev curr; curr nextTemp; } return prev; }核心思路是每次循环中先保存当前节点的下一个节点然后把当前节点的next指向前一个节点再整体往后移动。这个流程用三个指针prev、curr、nextTemp固定住了链表连接关系不容易写乱。如果笔试环境允许用Swift或者Objective-C同样思路的OC实现要注意一点反转后需要把原head的next置为nil否则会形成环。编程题最容易丢分的不是算法本身而是边界条件。比如字符串反转这类题要主动考虑空字符串、单个字符、包含空格和特殊符号的情况。建议做题时先花30秒把边界情况写在草稿纸上写代码时一步步对照这样比写完再检查高效得多。4.2 开放设计题如何把“设计一个XXX”答出层次网易的笔试偶尔会在最后放一道开放设计题比如“设计一个图片缓存库”“设计一个网络请求模块”“设计一个日志上报系统”。这类题没有标准答案但踩分点非常明确看你的思路有没有层次感。很多人拿到题目直接开始写代码甚至写成一坨管理类的方法集合。这其实是大忌。开放设计题的第一步永远是搭框架。以“设计一个图片缓存库”为例我的答题思路是这样的先拆需求图片缓存要解决什么问题避免重复下载、加快展示速度、节省流量。核心功能可以拆解为内存缓存、磁盘缓存、三级缓存策略、缓存淘汰策略、线程安全。再给架构内存缓存使用NSCache因为它在内存紧张时自动清空比NSMutableDictionary更合适可以设置countLimit和totalCostLimit避免内存无限增长磁盘缓存把图片数据写入沙盒Caches目录注意iOS系统可能清空Caches目录所以不能放重要用户数据也可以用文件hash后的字符串做文件名检索顺序先从内存缓存查命中直接返回没命中再查磁盘命中后读文件并回填内存都没有就去网络下载下载成功先写磁盘再写内存缓存淘汰NSCache自带LRU近似策略磁盘缓存可以按图片最后访问时间排序超过阈值就删除线程安全内存缓存的读写可以放在一个串行队列里或者用锁保护网络下载的图片回调一定要回到主线程刷新UI。最后补上异常处理图片下载失败的重试机制、超大图片的解码卡顿问题、内存警告时手动清理内存缓存。这样写出来即便代码一个字没写面试官也能看出你是个有工程思维的人。比直接贴100行代码有价值得多。4.3 算法题的时间复杂度分析为什么网易会要求你写网易的编程题一般不强求最优解但非常在意你有没有分析复杂度的习惯。很多大一大二的同学刷题只看“通过了”从不分析时间复杂度和空间复杂度这在美团、网易、腾讯这类笔试里都会吃暗亏。一个比较有代表性的例子是“求两个链表的交点”。最直接的思路是双重循环对链表A的每个节点遍历链表B去找相同节点时间复杂度O(mn)。如果题目没有额外限制这个解法能跑通但如果你在答题时写了更优的双指针解法struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) { if (headA NULL || headB NULL) return NULL; struct ListNode *pA headA, *pB headB; while (pA ! pB) { pA (pA NULL) ? headB : pA-next; pB (pB NULL) ? headA : pB-next; } return pA; }解法思路是两个指针分别从两个链表头开始走走到末尾后换到另一个链表继续走。如果两个链表有交点它们会在交点处相遇因为各自走过的路径长度相同都是A的长度加B的长度。如果没交点最终都会走到NULL。这样写时间复杂度降到了O(mn)空间复杂度O(1)同时还能展示你对经典算法思想的掌握程度。在笔试答案里简单写上“时间复杂度O(mn)空间复杂度O(1)”这行字和裸写代码的观感完全不同。5. 从笔试到面试复盘这套题背后的备考路线5.1 知识点优先级排序哪些是必须拿分的复盘完网易2018这套iOS实习生笔试题我把它背后真正想考察的知识点按优先级排个序方便大家对照自查。这个排序不仅适用于网易对腾讯、字节、美团等大厂的iOS实习生笔试也基本适用。第一优先级是内存管理和ARC。这是iOS开发的重中之重循环引用、weak原理、autoreleasepool、属性修饰符每一个子知识点都值得深挖。不仅要会用还要能在纸上画出引用计数的变化流程。第二优先级是Runtime。消息机制、isa指针、分类、关联对象、Method Swizzling这五块是笔试和面试的双热点。建议直接去读一遍objc4源码中objc_msgSend、class_ro_t、method_t相关的数据结构看懂之后很多题根本不用背。第三优先级是多线程和RunLoop。GCD的队列模型、死锁场景、线程安全方案以及RunLoop的mode切换和卡顿监控是判断候选人是否“真正懂iOS底层运行机制”的标准考题。第四优先级是网络和计算机基础。HTTP、HTTPS、TCP是必考C语言指针和内存是基本功网络协议考得深不深看公司风格但基础概念不能丢分。第五优先级是算法和数据结构。大厂笔试基本都会有一两道编程题建议把LeetCode简单到中等偏易的题刷到100题以上重点是链表、字符串、栈、队列、二叉树这几个类别。5.2 面试追问方向笔试里每一个答案都能变成一个问题备考笔试时一个很有效的策略是把每一道题当成面试题的“子问题”来准备。网易笔试里有一道“block是否会把栈上的变量复制到堆上”的选择题你可能选对了但如果接下来面试官追问“什么情况下block在栈上什么情况下在堆上复制过程中捕获的变量发生了什么”你还能接得住吗这里面有一个很经典的“陷阱”在ARC环境下block通常在以下情况会被自动copy到堆上block被赋值给一个strong属性或强引用变量、block作为返回值返回、block被传入一个会持有它的方法参数中。但如果你故意用__NSStackBlock__类型的block并在栈销毁后调用程序会崩溃。知道这套完整链路的人才算真的理解了block的内存管理模型。再举个例子笔试答案里只要出现“循环引用”这个词面试官就可能接着问“block对什么变量是引用捕获对什么变量是值拷贝”。正确答案是局部变量默认值拷贝__block修饰的变量是通过一个结构体指针进行引用捕获的所以修改变量的效果能反映到block外部而对象类型的局部变量即使被捕获了如果没有在block外再用block也不会持有它导致循环引用。备考时养成这个习惯——“任何知识点都往后多想一步”笔试通过后你会发现自己面对面试可以从容很多因为面试题的大方向已经被你主动预测过了。5.3 几个容易忽略的坑最后分享几个当年很多人踩过的坑虽然都是细节但笔试中往往就是这些细节决定了能不能进面试。第一答题前先把整套题快速过一遍。网易的题量大选择题加简答题加编程题时间其实并不宽裕。很多人一上来就死磕某一道编程题结果后面20分的选择题没时间做。策略是先花3分钟扫一遍所有题目把有把握的题做完再回来啃难题。第二简答题不要写太长。笔试阅卷和面试不一样阅卷系统通常是用关键词或者人工快速扫描的方式判分。写了一大堆但核心关键词没出现等于没答。好的习惯是先把结论和核心概念放在第一句再展开原理最后补充一个实际场景验证结论。第三编程题一定要先写思路再写代码。很多在线笔试都有答题框你完全可以在代码前用注释写一段思路描述比如“这道题先O(n)扫描一遍字符串用字典统计每个字符出现次数第二次扫描再检查字典里的计数找到第一个计数为1的字符”。这样即使代码没调通阅卷人也能看到你的思路拿到部分分。第四离线调试能力要提前练。在线笔试平台的调试能力和本地的Xcode差很多有时候连断点都不支持。建议提前在牛客、LeetCode这类在线环境里刷题适应“没有编译器提示、只能printf打印”的裸写状态。第五考场上心态比实力重要。2018年这套试卷我见过不少后台成绩不错的同学因为没睡好或者时间分配失误在基础题上连环翻车。笔试只是入场券后面还有多轮面试一场笔试不能定义你的全部水平稳住心态正常发挥把会的都写对就已经超过一半的人了。有个很实用的想法供大家参考如果你准备投大厂iOS实习不要等到官网开放投递才开始刷题。提前两个月把内存管理、Runtime、RunLoop这三个方向的知识用一个周末集中过一遍再用一周时间刷leetcode高频题最后做两套模拟题掐时间训练就比较稳了。这套方法我从很多学弟学妹身上验证过效果比考前突击背八股文好得多。
返回列表