尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

2018字节跳动iOS校招题复盘:底层原理与工程化考点解析

2018字节跳动iOS校招题复盘:底层原理与工程化考点解析 说个实话2018年字节跳动校招iOS方向第二批这批题在当年的开发者圈子里讨论度不低。原因倒不是题目本身有多“偏门”而是它把iOS知识体系里最容易被忽略的底层细节集中起来考了一遍。不管是当时正在找工作的应届生还是工作了两三年想跳槽的工程师能被这套题“打回原形”的都不在少数。我当时认认真真把这张卷子过了一遍后来又把涉及到的知识点全部翻书、查源码、写Demo验证了一遍收获比准备面试本身还要大。这篇文章就围绕这批题目做一个系统复盘不晒所谓的“原题答案”而是把题目背后真正想考察的iOS知识模块拆开讲清楚。内容涉及Objective-C底层机制、多线程、UI布局与渲染、网络与数据持久化、架构设计等核心领域也顺带聊一些备考时的思路和踩坑点。无论你正在准备iOS校招还是已经工作了想补一补底层基础都可以直接参考。1. 先搞清楚这套题到底在考什么1.1 考点分布比想象中更“工业化”很多人拿到这套题的第一反应是“怎么全是底层原理”其实这只是表象。把题目按知识点归类之后会发现出题人明显在模拟一个真实业务开发中会遇到的完整链路从代码怎么跑起来编译与Runtime到页面怎么渲染UI与布局再到数据怎么流动网络与持久化最后是模块怎么组织架构设计。这跟字节那种“工程导向”的团队风格一脉相承——他们招人不是为了招一个会写UI的而是招一个能独立解决复杂工程问题的人。当年网上有不少人吐槽这套题“太难了”“偏”但客观说它并没有超纲。每一个考点在Apple官方文档、SDK源码、知名开源库里都能找到对应答案只是平时写业务代码时很少会思考到这一层。比如Runtime的消息转发、自动释放池的释放时机、约束的优先级计算这些东西你天天在用但未必说得清底层是怎么实现的。1.2 第二批和第一批的差异在哪里有心的读者可能会去对比第一批和第二批的题目两者的风格确实有差异。第一批更偏重基础语法和常用API的考察第二批次明显加深了底层原理和性能优化的比重。这可能和岗位方向有关也可能只是出题人想做出区分度。不管原因是什么对后来者有一个明确的参考意义iOS面试的深度是逐年递增的光会写代码远远不够必须对底层机制有真正的理解。我当时复习时专门做了个表格把两批题目的知识点模块列出来对照大致分布如下知识点模块第一批侧重第二批侧重OC语法基础属性、分类、协议分类加载、关联对象、方法交换Runtime消息发送基本流程消息转发、动态方法解析、KVO实现内存管理ARC基本规则weak底层实现、自动释放池、循环引用多线程GCD基本用法队列层级、死锁场景、多线程安全UI布局Frame与BoundsAuto Layout原理、约束冲突、UIStackView网络会话与请求HTTPS流程、抓包原理、缓存策略架构MVC各层职责MVVM、组件化、模块间通信这张表帮我建立了一个很清晰的知识地图后面复习基本就按这个框架走。建议大家也试着画一张属于自己的知识地图而不是东一榔头西一棒子地刷题。2. Objective-C底层机制校招笔试的重头戏2.1 Runtime消息转发不只要背流程Objective-C的Runtime是这套题里出现频率最高的知识点没有之一。消息发送、消息转发、动态方法解析这三层机制几乎是必考题。常规的准备方式是把苹果官方文档里的转发流程图背下来但笔试中常见的坑在于它往往会给你一段代码问你某个方法调用最终会走到哪一步或者交换方法之后为什么会出现死循环。举个例子写一个NSObject的分类在load方法里交换viewDidLoad和自定义方法implementation UIViewController (Swizzling) (void)load { static dispatch_once_t onceToken; dispatch_once(onceToken, ^{ Class class [self class]; SEL originalSelector selector(viewDidLoad); SEL swizzledSelector selector(swizzled_viewDidLoad); Method originalMethod class_getInstanceMethod(class, originalSelector); Method swizzledMethod class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } - (void)swizzled_viewDidLoad { [self swizzled_viewDidLoad]; // 自定义逻辑 } end这段代码有两个关键点。一是为什么要在load里用dispatch_once保证只执行一次二是为什么先用class_addMethod判断。class_addMethod的返回值表示是否成功添加方法如果当前类已经实现了originalSelector则添加失败此时直接用method_exchangeImplementations交换两个实现即可。如果当前类没有实现originalSelector而是继承自父类直接交换会把父类方法实现也换掉造成全局污染所以要先添加后替换。这种细节在平时的业务代码里看不到但在笔试里就是区分度所在。另一个容易被追问的点是交换后的方法为什么不会死循环在swizzled_viewDidLoad里调用[self swizzled_viewDidLoad]因为两个方法的IMP已经被交换所以这行代码实际执行的是原来的viewDidLoad而不是自己调自己。如果交换逻辑写错在load里重复执行了多次交换IMP就被来回换回去了才会出现预期外的行为。2.2 消息转发流程笔试中的典型代码题消息转发的三道防线必须熟练掌握动态方法解析resolveInstanceMethod、快速转发forwardingTargetForSelector、完整转发methodSignatureForSelector forwardInvocation。笔试常见考法是给你一段代码问某个类收到一个未实现的选择子后依次会调用哪些方法最后有没有崩溃。我建议把这些方法全部实现一遍然后在方法里加日志跑起来看调用顺序implementation MyObject (BOOL)resolveInstanceMethod:(SEL)sel { NSLog(Step 1: resolveInstanceMethod %, NSStringFromSelector(sel)); // 如果没有动态添加方法返回NO进入下一阶段 return [super resolveInstanceMethod:sel]; } - (id)forwardingTargetForSelector:(SEL)selector { NSLog(Step 2: forwardingTargetForSelector %, NSStringFromSelector(selector)); // 返回一个能处理该消息的对象 return [super forwardingTargetForSelector:selector]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSLog(Step 3: methodSignatureForSelector %, NSStringFromSelector(aSelector)); return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(Step 4: forwardInvocation %, NSStringFromSelector(anInvocation.selector)); } end运行之后能看到一个完整的调用链。真正理解这条链的价值在于很多开源库的AOP方案、消息防崩溃方案、热修复方案本质都是在这三层里做文章。笔试考它不是为了让你背流程而是检验你是否具备“在系统级机制上做工程”的意识。2.3 weak的底层实现几乎是必问property修饰符里weak为什么能自动置nil底层其实是一个__weak哈希表结构键是被弱引用的对象地址值是一个数组存放所有指向该对象的weak指针地址。当对象被释放时Runtime会遍历这张表把每个weak指针都置为nil。当年笔试里有一道题很典型为什么weak修饰的变量在ARC下不需要担心野指针而assign会因为assign只是简单地把指针指向对象的地址对象销毁后指针仍然指着那块内存而这块内存可能已经被复用就成了野指针。weak则会在对象销毁时由Runtime主动把指针置空。顺着这个考点还可以延伸到dealloc里能不能访问weak变量、weak变量在栈上能不能用、以及为什么weak只能修饰对象类型。这些都是一组连贯的基础知识。3. 多线程与并发笔试中的固定菜单3.1 GCD的队列层级关系别只背表格iOS多线程的知识点里GCD队列和死锁场景是每年必考。很多资料喜欢列一个表格串行队列、并发队列、主队列、全局队列、同步执行、异步执行两两组合会得到什么结果。表格本身没错但光背表格容易在遇到变体题时翻车。推荐的做法是自己写代码验证每一组组合。比如异步加并发队列同时提交多个任务观察顺序和线程号- (void)gcdDemo { dispatch_queue_t queue dispatch_queue_create(com.example.concurrent, DISPATCH_QUEUE_CONCURRENT); for (int i 0; i 5; i) { dispatch_async(queue, ^{ NSLog(Task %d, thread: %, i, [NSThread currentThread]); }); } }运行结果会显示任务交替执行且可能出现在多个不同线程上。而把队列改成串行再改成同步执行分别观察输出你会对“同步/异步决定是否阻塞当前线程”和“串行/并发决定任务在队列内部如何执行”这两句话有非常直观的理解比背表格牢固得多。笔试中常见的一个变体是“在串行队列里执行dispatch_sync会怎样为什么”。答案是在同一个串行队列里用同步提交任务会发生死锁原因很简单同步提交会阻塞当前线程等待任务执行完毕但当前线程正好在串行队列里任务无法执行于是互相等待。但如果是在并发队列里dispatch_sync就不会死锁因为任务可以分到另一个线程执行。3.2 多线程安全与常见崩溃场景线程安全的知识点经常和实际崩溃场景绑定在一起考察。最常见的就是可变容器在多线程环境下写入导致崩溃比如在一个全局数组里同时addObject。这时候面试官会追问为什么数组会崩溃本质上是内部状态被并发修改导致内部结构不一致。工程上常用的解法是栅栏barrier或者锁。笔试对锁的考察通常到达“知道有哪些锁、各自特点”的程度即可但为了稳妥最好能把synchronized、NSLock、dispatch_semaphore、os_unfair_lock的区别说清楚。出题人往往喜欢让你比较它们的性能特点以及为什么在Swift中不建议用NSLock而建议用os_unfair_lock或Actor以2018年来说Swift并发可能还没那么突出但能主动提出来会加印象分。我特别强调一件事iOS开发里很多线程安全问题的根源其实是把UI操作放到后台线程。这一点在后面的渲染优化里还会提到。它属于“知道原理但控制不住手”的典型问题也是笔试题里经常用来做陷阱的场景。3.3 死锁排查思路笔试之外的实战价值死锁的笔试题通常给你一个场景让你判断会不会死锁然后说明为什么。这个知识点单独看很枯燥但在实际工程里排查卡顿问题时非常有用。这里分享一个我后来的经验真机调试时如果UI卡死先暂停App看主线程的调用栈。如果停在某个锁的wait上就用命令查看哪个线程持有锁然后顺藤摸瓜。这种排查思路放在校招笔试阶段可能超纲但在后续的实际工作中是高频操作。所以我在复盘这批题时专门把这部分沉淀成了自己的排查流程确认卡死是否只发生在特定操作上。Xcode暂停后看主线程栈定位锁等待位置。在debugger里用thread list和frame info确认持锁线程。检查持锁线程是否也在等待另一个锁形成循环等待。这套流程后来被我用在了不止一次的线上问题排查中属于“笔试考的是概念工作考的是流程”的典型对应。4. UI与布局从Frame到约束的深度理解4.1 Auto Layout的本质与约束算法2018年这批笔试里UI布局相关题目比往届多了不少。这可能跟当时的行业背景有关——iPhone屏幕尺寸越来越多纯代码Frame布局已经跟不上适配需求Auto Layout成为主流方案。出题人自然会考察约束相关的底层原理。Frame布局和Auto Layout的核心区别在于后者是一个“求解约束方程组”的过程。系统需要根据约束条件计算出每个视图的实际frame这个过程叫“约束求解”。一个优秀面试者应该能解释为什么在frame设置后再添加新约束会导致frame不对为什么需要setNeedsLayout和layoutIfNeeded为什么约束冲突会在控制台抛出那些难看但信息量很大的日志举个笔试中常见的题目给一个视图添加了leading/trailing/top和固定高度约束又设置了centerX约束问会怎样。答案是这个约束集合是过约束的因为leading/trailing和centerX之间存在冲突。系统会通过降低约束优先级来自动修复或者直接忽略其中一个约束。这个场景在实际开发里很常见只是Xcode有时候会在控制台默默输出日志不仔细看就忽略了。4.2 UIStackView的引入与使用要点UIStackView在iOS 11之后基本成为页面布局的默认选择和Auto Layout的配合关系非常紧密。2018年时它已经不算新特性了但笔试中专门考察它的也不少。最常见的考点是UIStackView的axis、distribution、alignment三者的区别以及为什么用arrangedSubviews而不是subviews来管理子视图。很多人会用UIStackView但说不清它在Auto Layout上做了什么简化。其实UIStackView内部就是帮你自动生成了一组约束关系你不用再手动添加间距、对齐、等宽等约束它根据排列方式自动算好。一个容易踩坑的细节是给UIStackView里的子视图设置hidden属性时如果对UIStackView本身执行了动画子视图的隐藏和显示动画很容易不生效。原因是在动画闭包里直接改hidden时UIStackView对布局的更新时机与预期不一致。解决办法是加上layoutIfNeeded调用[UIView animateWithDuration:0.3 animations:^{ self.stackView.arrangedSubviews.lastObject.hidden YES; [self.stackView layoutIfNeeded]; }];这类细节在官方文档里通常不会刻意强调但在实际开发中非常影响体验是笔试里通过场景题考察“有没有真实写过代码”的手段之一。4.3 离屏渲染与圆角优化性能题的常客iOS界面卡顿优化的核心理论中离屏渲染是绕不开的。笔试里常考的代码点是给一个UIView直接设置cornerRadius和masksToBounds会发生什么。短答案是触发离屏渲染。更进一步的追问则是为什么圆角会引发额外的渲染开销以及如何优化。离屏渲染的含义是在GPU当前屏幕渲染流程之外额外创建一个渲染缓冲区在其中完成图层的合成计算再提交到屏幕上。这个过程的代价比直接在当前缓冲区绘制要高。优化圆角的常见方案包括用UIBezierPath画圆角、在图片上预先生成带圆角的图、只对内容层设置圆角而避免触发masksToBounds。实际的优化策略要看场景。如果是一个需要频繁滚动的列表每个cell都有圆角头像最好的办法是提前把头像裁成圆角图片而不是运行时让系统去裁剪。如果只是静态页面iOS 11之后可以通过layer.cornerCurve和iOS 13的UIView.CornerCurve做平滑处理本身不会有大量离屏渲染问题。这个知识点表面在考渲染实际在考工程权衡能力。5. 网络与数据持久化从抓包到离线方案5.1 HTTPS协议与抓包原理网络协议在iOS笔试里的考察程度通常点到“知道HTTP与HTTPS的区别、知道TLS握手过程、知道证书验证机制”为止。但第二批的题明显把难度往上提了一点它会让你分析Charles抓HTTPS包的原理以及为什么App在代理环境下会出现证书校验失败。Charles抓HTTPS包的原理本质上是中间人攻击的合法化使用。它的证书被安装到手机信任列表里然后由它替代服务器与App建立连接再用服务器的证书与App建立另一个连接。App如果设置了证书固定SSL Pinning对服务器证书做了本地校验抓包就会失败。2018年很多公司开始做高安全性App证书固定相关内容在校招里出现频率很高。要回答这种题需要理清TLS握手的基本流程客户端发起ClientHello服务器返回ServerHello和证书客户端验证证书并协商密钥之后用对称加密通信。证书固定的实现方式有两种一是内置公钥做本地比对二是要求服务器证书必须匹配某一个CA签发的特定证书。后者在证书过期时需要发版更新所以业界更常用公钥固定。5.2 数据持久化方案对比以及沙盒机制iOS沙盒目录结构是笔试里的送分题但出了点变体就容易出错。Documents、Library/Caches、tmp三者的区别要烂熟于心Documents用于保存用户生成的重要数据会备份到iCloudCaches用于缓存临时数据系统可能随时清理tmp存放临时文件也是随时可能被清。笔试里的几个经典问题如下为什么数据库文件不建议放在tmp目录因为系统可能在任何时机清空tmp导致数据丢失。为什么大文件缓存建议放在Caches而不是Documents因为Caches不会被iCloud备份可以节省用户的云端存储空间。为什么归档NSKeyedArchiver不适合存储超大数据因为它一次性把所有数据加载进内存数据量大了内存会顶不住。针对“大量数据持久化”的考题常见的回答思路是使用SQLite或者基于SQLite做封装比如FMDB、WCDB。2018年那会儿WCDB开始逐渐被业界接受如果能在笔试里提到SQLite的WAL模式、索引优化、事务批量写入会是明显的加分项。不过面试官通常不会在校招阶段要求你手写SQLite底层实现能说清楚“为什么项目里选择SQLite而不是直接Archive”就很足够了。5.3 离线缓存方案的设计思路网络模块还有一个很常见的考点一个需要离线使用的App应该怎么设计缓存方案。这里不是问具体API而是考整体设计思路缓存策略是用“缓存优先、网络兜底”还是“网络优先、缓存兜底”缓存数据要放在哪里不同业务的数据缓存策略是否相同我当时的回答思路是拆成三个层面接口层区分实时数据和准实时数据。实时数据如消息走网络优先离线时再降级到缓存准实时数据如文章列表走缓存优先后台刷新。存储层图片和音视频这种大文件放Caches结构化数据放SQLite用户配置用小文件Archive。一致性层设置缓存过期时间以及版本号服务端推送更新时主动清理本地缓存避免脏数据长期驻留。这个思路不是标准答案但胜在能体现完整思考链路比单纯背API要好得多。6. 架构设计与工程化从MVC到组件化的演进6.1 MVC的局限性与MVVM的引入iOS开发中架构设计相关的题目在2018年校招里开始增多。毫无疑问MVC是基础认知但面试官更想知道你是否理解MVC在大型项目里的问题ViewController越来越臃肿业务逻辑与UI逻辑难以分离测试性差。MVVM的核心思路是把原来ViewController里属于数据处理的逻辑抽到ViewModel里Controller只负责绑定和事件转发。笔试常见考法是给你一个登录页面要求用MVVM来描述各个角色的职责。这时候要能说清楚Model负责什么、ViewModel暴露什么、Controller怎么绑定以及双向绑定在iOS里怎么实现KVO、Delegate、闭包、响应式框架。需要明确的是MVVM本身也不是银弹。它在中小型项目里带来的额外复杂度可能比它解决的问题还多。这点在面试里主动讲出来反而会让面试官觉得你是有工程判断力的不是只会背名词。6.2 组件化与模块间通信大厂项目里的土话题组件化在2018年是移动端面试的一个明显趋势字节、阿里、美团等大厂都在做。笔试时未必会直接让你设计一个完整的组件化方案但很可能会问“多个业务模块之间怎么通信”。如果只回答AppDelegate传值那基本就出局了。业内比较成熟的方案有几种路由中间件、Target-Action、Protocol-Class匹配、依赖注入容器。每种方案的优劣需要能展开讲否则容易在面试官的追问里掉链子。以消息路由为例核心是维护一张“URL / 类名 / 方法名”的映射表通过中心调度器转发。这种方案能解耦调用方和被调用方但缺点是集中维护表成本高编译期无法检查字符串正确性。所以后来的实践里很多人会把URL改成block注册或者在编译期用宏做检查。这部分属于“知其然还要知其所以然”的典型内容。6.3 设计模式与代码分层笔试里的隐藏分除了架构模式笔试里也会直接考一门设计模式。题目可能是“单例模式有哪些风险和替代方案”也可能是“你怎么设计一个缓存类”。我当时答题时发现这部分比前面的底层原理更容易得分因为问题非常务实只要平时写过工程代码基本都能聊几句。设计模式在iOS里最常见的应用场景其实是“避免重复造轮子”用工厂方法统一创建对象、用策略模式处理不同的支付渠道、用观察者模式处理事件广播。如果表达清晰面试官可以判断你具备一定的抽象能力和业务建模意识。7. 常见问题与排查技巧实录7.1 笔试中容易失分的细节复盘这套题时我发现很多失分点不在知识本身而在细节。比如属性修饰符里copy和strong的区别很多人知道NSString要用copy但如果把它换成NSMutableString再问“原对象改变后属性会不会变”有些人就答不上来了。本质是拷贝的是深拷贝还是浅拷贝以及可变对象和不可变对象在拷贝时的行为差异。还有自动释放池相关的题目难点在于“在MRC时代和ARC时代自动释放池的触发时机有何不同”。这是个有历史包袱的知识点但确实能检验你对iOS内存管理机制的理解深度。笔试里只要出现这种题基本能过滤掉一半人。这里有一个小结表格是我当时根据自己的失分点整理的常见失分点错误理解正确理解copy修饰可变字符串copy后改原值会影响属性不可变拷贝生成新对象原值改变不影响属性自动释放池代码块结束立即释放在RunLoop循环结束时统一释放UIStackView子视图隐藏直接改hidden即可动画里需要配合layoutIfNeeded离屏渲染只有圆角才有离屏渲染shadow、mask、shouldRasterize等也会触发串行队列同步任务一定死锁只有同一个串行队列才会死锁7.2 调试工具的真实用法不仅限于LLDB笔试中如果考到调试相关的题目通常会和线上问题挂钩。了解LLDB基本命令是必备的但更值钱的是会使用Instruments的性能分析工具。2018年时在笔试里提到“我会用Time Profiler定位卡顿”已经是一个很不错的亮点了。实际操作时Time Profiler可以帮你定位到某个方法占用的CPU时间结合主线程调用栈即可快速找到卡顿源头。如果想排查内存泄漏可以用Leaks工具配合Xcode的内存图调试器基本能覆盖90%的泄漏场景。对于“为什么TableView滚动不流畅”这类经典面试题这些工具是你验证优化方案效果的关键。7.3 从笔试复盘到真实项目的避坑心得笔试中的很多知识点到了真实项目里往往是以一种“问题”的姿态出现的。比如你在项目里看到某个页面的圆角头像在滚动时会闪一下这就是离屏渲染优化没做好的表现。你费了半天劲排查最后发现只是少了个shouldRasterize或者图片处理阶段没有做圆角裁剪。我在复盘这套题之后最大的收获是心态上的转变不再把笔试面试当作一个独立的应试环节而是当作一次全面的查漏补缺。后来工作里遇到类似问题我会在心里把这些知识串起来形成自己的排查思路。8. 后续还可以怎么扩展学习8.1 Swift、跨平台与新的UI框架2018年那会儿Swift还处于4.x时代和现在的Swift并发、宏、SwiftUI已经有了很大不同。如果现在再去看当年那批题知识框架是没问题的但具体技术点已经迭代了好几轮。比如UIStackView在iOS 16之后又有了新的配置方式页面的布局方式也从手写约束逐渐转向SwiftUI的声明式布局。我的建议是基础原理永远值得学但要在新框架里重新验证一遍。比如Auto Layout的约束原理在SwiftUI里变成了“布局协议”的概念核心思想一脉相承只是表达方式变了。理解旧知识是为了更快地掌握新知识。8.2 证书、签名与自动化工程链路的延伸虽然是校招笔试复盘但如果你对iOS开发的整体工程链路有兴趣建议往证书管理、代码签名、CI/CD自动化方向扩展学习。这些内容不会直接出现在校招笔试里但进入公司之后很快就会接触到。比如每次Xcode升级后证书失效、描述文件过期导致的无法真机运行本质上都属于开发环境管理的问题和底层技术无关却很影响开发效率。学习路径可以是这样先搞懂证书和描述文件到底是干什么的再去看Fastlane这类自动化工具怎么配置签名最后再到云端打包、自动发布。这条路走通之后你对iOS工程的理解会有一次明显提升。8.3 坚持写笔记把知识变成生产资料最后聊一个习惯层面的建议找一套自己的记笔记方法把每次复习的知识点沉淀下来。我早期做题时习惯把每道题直接写在笔记本上后来发现知识特别零散。后来我改用“问题-原理-案例-坑”四段式每个知识点记一页效果好了很多。以RunLoop为例我的笔记格式是这样的问题RunLoop的本质是什么为什么主线程默认有RunLoop而后台线程没有原理RunLoop是一个事件循环机制可以监听事件源休眠时不会占用CPU有事件时唤醒处理。案例NSTimer在滚动TableView时为什么不准因为默认情况下Timer被添加到DefaultMode滚动时RunLoop切换到了TrackingModeTimer不被处理。坑解决方法是把Timer添加到CommonMode或者改用DispatchSourceTimer。这种写法看起来简单但对后来的复习和实际工作帮助极大。它逼着你在记录时不断追问“为什么”而不是浮于表面的抄写。总体来说这套2018年字节跳动iOS校招第二批的题放在今天依然是很好的自我检测工具。它的价值不在于让你背到一份所谓的“标准答案”而在于帮你把iOS知识体系重新梳理了一遍。如果你能沿着本文提到的这些模块逐一把代码写一遍、把原理讲清楚那么不管面对哪家公司的iOS面试你的底气都会足很多。
返回列表