ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

iOS开发十年:从Objective-C到SwiftUI的踩坑与成长实录

iOS开发十年:从Objective-C到SwiftUI的踩坑与成长实录 2015年夏天我在一台内存只有4GB的MacBook Air上编译第一个完整的Objective-C项目。进度条卡在最后百分之二的地方风扇狂转我盯着Xcode那个转圈的菊花心里只有一个想法这条路我是不是选错了。那一年我给自己起了一个网名叫Ioser。不是拼错了iOSer是故意少打了一个字母。因为那段时间我真的觉得自己像个loser——别人在朋友圈晒旅行和offer我在出租屋里和ARC、循环引用、Auto Layout死磕。但loser和iOSer之间其实只差一个字母的觉悟。十年后回头看这个拼错的名字反而成了最准确的注脚在iOS开发这条路上你既要接受自己经常被问题按在地上摩擦的事实又得咬着牙把每一个没解决的Bug变成下一个版本的Release Notes。我入行那年iPhone 6s刚发布iOS 9刚推送Swift 2.0才在WWDC上露面整个圈子还在为“到底要不要从Objective-C迁到Swift”吵得不可开交。当时谁能想到十年后的2025年SwiftUI已经成为新项目的默认选择App Store的审核规则改了无数轮跨端框架从React Native到Flutter再到HarmonyOS一轮一轮地冲击着原生开发者的阵地。这篇文章不是教程不是广告也不是年度总结。它是一个从2015年一路写代码写到2025年的普通iOS开发者的备忘录——记录我踩过的坑、做过的选择、错过的机会以及那些深夜加班到崩溃时悟出来的道理。写给刚入行的新人看写给正在纠结转型的同行看也写给十年后那个可能还在写代码的我自己看。如果你正好也在iOS开发这条路上走了几年我相信你能在这篇文章里找到自己的影子。1. Ioser这个名字是我给自己立的flag1.1 为什么是loser为什么又是ioser先把这个名字拆开聊。Ioser去掉字母o就是loser加上字母s是iOSer再往前一步I OS er意思是“站在操作系统上的人”。这个名字放在一起恰好是iOS开发者的真实生存状态——你天天和操作系统打交道但操作系统也天天让你觉得自己像个菜鸟。2015年我刚接触iOS开发的时候最崩溃的瞬间往往是这类场景一个界面明明照着文档写的跑起来就是显示不对一个内存问题怎么查都查不出来后来才发现是block里捕获self造成了循环引用。那时候没有人告诉你你踩的每一个坑早在十年前就有人踩过区别只是你是否愿意承认自己是个loser然后老老实实地去翻文档、看堆栈、跑Instruments。我后来带过不少新人发现一个规律那些进步最快的人往往不是天赋最高的而是最愿意承认“我不懂”然后马上去查的人。loser心态其实是一种很好的学习状态——只有觉得自己不够好你才会去改变。反而是那些刚入行就觉得自己什么都会的人三年以后还在用三年前的写法。1.2 十年我们经历了什么2015年到现在iOS开发领域的变化用天翻地覆来形容一点都不过分。从系统版本看我们经历了iOS 9到iOS 26每一代都带来新的API、新的设计语言、新的适配要求。从开发语言看Swift从2.0一路走到6.0经历了三次大版本重构API风格变了又变The Composable Architecture、Swift Concurrency这些概念一个接一个冒出来。从UI范式看我们经历了纯Frame布局、Auto Layout、Size Classes、Masonry/SnapKit的时代又迎来了SwiftUI的声明式UI革命。更不用说开发工具链的变化。2015年调试内存问题要靠Instruments里的Zombies后来有了Xcode自带的Memory Graph Debugger以前写网络层要用AFNetworking现在直接用URLSession加async/await以前App审核要等一周以上现在有了TestFlight可以更快地分发给测试人员。这些变化叠在一起构成了一个开发者的完整十年。你会发现没有哪一项技术是永远不变的但底层的能力——理解内存、理解并发、理解系统机制、理解用户需求——始终是安身立命的根本。1.3 这篇文章能给你什么我自己早年看技术文章最怕那种从头列到尾的“API大全”看完什么也记不住。所以这篇文章我尽量不用编年体而是用专题的方式把十年间最有价值的经验和思考拆开来讲。第二章聊技术演进的底层逻辑帮你看懂为什么SwiftUI会取代Auto Layout、为什么Swift会一直变第三章讲职业方向的选择包括Swift转型、跨平台冲击和架构选型第四章是我从真实项目里捞出来的崩溃、卡顿和审核被拒案例每一步排查过程都还原给你第五章是如果让我重新带一个新人我会让他怎么学、学什么以及我自己十年后还在坚持的四个习惯。如果你时间有限可以直接跳到第四章看踩坑复盘那是我觉得全篇最有“现场感”的部分。当然我更建议你从头读一遍因为很多后期的判断都是建立在前期的积累之上的。2. 十年技术线iOS 9到iOS 26从手写Frame到SwiftUI2.1 硬件与系统的底座变化从2GB内存到8GB内存很多新人可能无法理解为什么老开发总爱念叨“想当年”。其实是因为每一代iOS开发范式背后都是硬件和系统能力的支撑。2015年iPhone 6s的内存是2GB现在是8GB甚至更高当年的屏幕适配要做到4.0英寸、4.7英寸、5.5英寸三套尺寸适配现在要适配iPhone SE到Pro Max的多个尺寸还要兼顾iPad和折叠屏的布局。屏幕从32位到64位从RGB到P3广色域从普通刷新率到ProMotion 120Hz自适应刷新率。这些硬件参数的变化直接影响着我们在代码里怎么做布局、怎么管理图片资源、怎么处理动画性能。系统层面的变化更是巨大。iOS 10开放了更多的系统框架SiriKit让App可以接入语音助手iOS 12的ARKit把增强现实带到了手机上iOS 14引入了Widget和App ClipsiOS 16的Lock Screen自定义带来了新的桌面小组件生态iOS 17之后交互式Widget、Journal API、StandBy模式每一个新特性都是新的产品机会。所以你看在iOS开发里从来不缺新东西可学。但关键是不要为了学而学而要看清楚每一项新技术到底解决了什么问题——它是为了提升开发效率还是为了提供新的用户体验还是为了修补上一代框架的短板。带着这个问题去看待每一年的WWDC你会比那些只追新API的人领先一个维度。2.2 语言演进Swift三次“换血”的教训Swift这十年的演进史本身就是一部iOS开发的活教材。2015年我刚学Swift 2.0的时候它和Objective-C混编语法还不稳定函数标签、可选项、错误处理模型和后来差别很大。到2016年Swift 3.0推出几乎是“推倒重来”式的大清洗——API命名规范大规模调整第三方库集体改名好多项目光是适配就花了一两周。当时社区哀鸿遍野有人甚至说Swift每年换一次语法还不如继续用Objective-C稳当。但回头看那次”痛“换来了后续十年的稳定和繁荣。Swift 4.0带来了Codable协议Swift 5.0实现了ABI稳定Swift 5.5引入了async/await和ActorSwift并发模型开始成熟到Swift 6.0严格并发检查让数据竞争在编译期就能暴露大部分问题。这给我最大的教训是对于一门还在演进中的语言不要急着把全部身家押上去但一定要保持跟进。新项目可以保守地使用稳定特性老项目可以渐进式迁移但绝不能封闭自己对新技术的学习。很多老同事之所以在职业生涯后期感到吃力不是因为年纪大了学不动而是在Swift 3.0那次剧烈的变化后选择了退守Objective-C的舒适区结果越退越多到SwiftUI时代已经彻底接不过来了。2.3 UI范式Frame、Auto Layout、SwiftUI的三代框架UI开发方式的变迁是iOS开发十年最直观的变化。最早我们写界面用纯代码设置Frame。那时候每适配一个屏幕尺寸就要手动计算坐标屏幕旋转了还得在didRotate方法里重新算一遍。这样写的代码不仅冗长而且极其脆弱——换一张分辨率不同的图布局可能就全乱了。后来Auto Layout出现用约束来描述视图之间的关系理论上可以让布局自适应任何屏幕。但实际用起来VFL语法反人类约束冲突报错晦涩难懂很多项目后来又选择了Masonry或SnapKit这类链式语法库才算把Auto Layout的可读性拉回来。这个时代的另一个特点是用Xib和Storyboard做可视化开发但多人协作时Storyboard冲突频发很多团队又回到了纯代码加SnapKit的老路。2019年SwiftUI登场宣告了声明式UI时代的到来。你不再描述“怎么摆放”而是描述“状态一变界面应该是什么样”。这背后是数据流驱动的编程思想让界面与状态之间建立了单向绑定关系代码量大幅减少可维护性明显提升。但SwiftUI也不是没有坑——早期版本性能一般、报错信息极其抽象、第三方兼容性差直到iOS 16、iOS 17之后才逐步成熟。我的判断是未来两三年新项目的UI层会全面走向SwiftUI但存量项目的UIKit代码还会存在很长时间。一个成熟的iOS开发者不应该只会其中一个而是应该理解两者各自擅长的场景以及如何把老的UIKit组件桥接到SwiftUI里。2.4 架构模式MVC、MVVM、TCA的争论与妥协除了UI框架架构模式也是十年间争论最多的话题之一。2015年前后iOS圈最流行的还是MVC。苹果官方推荐简单直接——但实际项目一复杂Controller就会疯狂膨胀几千行的View Controller比比皆是“Massive View Controller”成为程序员自嘲的黑话。后来MVVM出现了把数据和业务逻辑下沉到ViewModel让Controller瘦身。配合ReactiveCocoa或RxSwift这些响应式框架数据绑定让UI层和逻辑层解耦得更彻底。但引入响应式框架也有成本——学习曲线陡、调试困难、团队水平参差不齐时反而让项目变得不可维护。再后来VIPER、RIBs、The Composable ArchitectureTCA这些更重型的架构陆续进入视野。TCA强调单向数据流和测试友好但它那一套Reducer、Effect的样板代码量并不小更适合中大型团队。我自己心里的真实答案很简单架构没有银弹。用MVC能搞定就别上MVVM用MVVM能搞定就暂时别上TCA。真正重要的不是用了哪个名字而是团队是否理解“单一职责、数据流向清晰、可测试性”这些底层原则。我见过用MVC写得非常利落的项目也见过套着MVVM外壳但照样乱成一锅粥的烂摊子。3. 决定方向的三次深夜语言、架构、跨平台3.1 第一次Swift来了转不转2015年下半年Swift 2.0发布后我身边出现明显的分水岭。一部分人觉得Swift语法新颖、类型安全、前景光明开始在新项目里全面采用另一部分人觉得Swift当时性能不稳定、第三方库生态跟不上、ABI也不稳定打死不碰。我当时做了一个折中的决定老项目继续用Objective-C维护新模块尝试用Swift开发同时在业余时间通读一遍Swift官方文档把可选项、闭包、值类型和引用类型的区别这些核心概念吃透。这个决定后来验证是对的。Swift 3.0大改那阵子纯Swift的项目都受到冲击但我因为老项目还在用OC受影响很小。等到Swift 5.0 ABI稳定之后Swift进入成熟期我已经完成了语言层面的平滑过渡写起来一点都不费劲。这件事教会我一个通用的决策方法面对不确定性不要all in也不要回避而是把风险控制在自己能接受的范围同时保持对新事物的持续跟进。3.2 第二次MVVM是不是万能的2016年到2017年MVVM话题席卷iOS圈。各种博客、分享、开源项目都在讲MVVM有多好、MVC有多烂。我当时所在的公司也在推MVVM但推着推着发现问题了ViewModel开始膨胀变成了新的“Massive View Model”。深入调研之后我发现MVVM的核心价值不是把Controller拆成两个文件而是把“视图状态管理”和“业务逻辑”分离开来。如果你的业务逻辑本来就很简单硬上MVVM只会增加Presenting/Dismissing时的数据同步成本。相反对于那些状态复杂、交互频繁的场景比如一个表单页、一个购物车页面MVVM的数据绑定价值非常大。最终我在架构选型上形成了自己的原则视图复杂度高、状态多优先考虑MVVM页面简单、一次性使用就老老实实MVC团队对函数式编程熟悉、测试要求高再考虑TCA。这个原则一直沿用到现在成为我评审别人代码时判断架构是否合理的重要标尺。3.3 第三次面对跨平台浪潮坚守还是拥抱2017年前后React Native升温2018年Flutter发布2021年后HarmonyOS出现在手机端移动开发从双平台变成多平台团队开始做跨端抽象。“原生开发会不会死”这个问题每隔一两年就会被翻出来讨论一次。我的观察是跨平台框架解决的是“业务代码复用”问题但解决不了“系统能力深度整合”问题。一个App如果只是展示信息、调用标准API用跨平台方案没什么问题但一旦涉及复杂的手势交互、系统级性能优化、新系统特性的首发体验原生开发依然是最可靠的路径。面对跨平台浪潮我既没有彻底倒戈也没有原地不动。我的做法是把业务层尽量用跨端思路去抽象比如组件化、后端驱动UI、统一的数据层但系统层和体验层的核心模块依然用原生去实现。同时鸿蒙这样的新平台对我来说不是威胁而是新的机会——多平台并存意味着能在架构领域做更深的抽象设计这种能力恰恰是十年原生开发经验带来的核心竞争力。4. 爬坑实录崩溃、卡顿、审核三个真实案例的完整复盘4.1 EXC_BAD_ACCESS一场KVO引发的“悬空指针”追凶2017年有个线上问题让我印象特别深某个页面在特定操作路径下必现崩溃崩溃日志里只有一行EXC_BAD_ACCESS指向的是主线程上的一个系统库地址。最恶心的是这种崩溃不发生在开发环境只在用户手机上偶现非常难复现。我当时的排查链路是这样的先想办法拿到完整崩溃日志。通过友盟和Firebase Crashlytics收集到的堆栈显示崩溃发生在KVO回调触发时但当前栈里看不到任何业务代码典型的“野指针访问”特征。使用Instruments的Zombies工具来复现。方法是在Edit Scheme里启用僵尸对象检测然后在模拟器上按用户描述的操作路径一步步走。果然在某个页面销毁后再触发一次下拉刷新时Zombies报告了一个过度释放的访问。顺着Zombies指向的地址找到了崩溃的真正原因一个控制器在dealloc时没有移除被观察的属性而观察目标是一个已经被提前释放的单例。第二次触发操作时KVO向一个悬空指针发送消息于是崩了。修复方案其实很简单就是在dealloc里补上移除KVO的代码同时把那个单例改为强引用持有。但这件事给我的真正教训是崩溃日志里那一行“EXC_BAD_ACCESS”没有任何意义真正的线索在“谁在什么生命周期里访问了谁”这件事上。所以后来我要求团队所有KVO都统一封装绑定观察者的生命周期绝不允许在dealloc里只注册不移除。// 错误的写法只注册不移除 object.addObserver(self, forKeyPath: status, options: [.new], context: nil) // 正确的写法在deinit里成对移除 deinit { object.removeObserver(self, forKeyPath: status) }4.2 掉帧探案一个Cell里藏了80多条约束另一个典型案例是列表滑动掉帧。现象很明确列表往下滑动的时候帧率掉到30fps以下明显卡顿。这种问题在iOS开发里是最常见的性能投诉之一但真正定位到根因的过程往往比想象中曲折。我第一反应是看图片加载、网络请求这些常规嫌疑点。用Time Profiler跑了一遍发现时间并没有烧在图片解码上。再用Core Animation的Instrument检查发现卡顿主要集中在Auto Layout的约束计算上——但单个Cell的约束并不多。后来我把崩溃和卡顿的怀疑点扩大直接在调试器里打印了每个Cell的约束数量。结果吓一跳由于一个共用组件在不同页面复用在创建时往Cell的contentView上动态叠加了大量重复约束有的Cell里累计约束超过80条。约束数量的增长不是线性的而是呈指数级膨胀每一层视图都在参与约束求解最终把Auto Layout引擎拖垮了。修复方案分两步第一步约束去重改为在初始化时统一创建约束禁止在layoutSubviews里动态添加第二步是给Cell的高度做缓存避免每次滑动都重新计算高度。修复后帧率直接拉回60fps满帧。这事的教训是性能问题往往不是某个“大坑”而是几百个小坑叠加的结果。单个约束看起来没事80条约束叠加起来就是灾难。现在我做代码Review时会特别留意Cell里View的层级深度和约束数量超过阈值就直接打回重写。4.3 审核被拒虚拟支付、截图和与审核团队的沟通艺术App Store审核被拒是每个iOS开发者职业生涯里绕不开的一课。我印象最深的有三次。第一次是因为虚拟支付。App里有一个“打赏”功能用的是第三方支付通道没有走IAP。被拒之后我一开始是懵的——很多App都这么做凭什么拒我后来研读了审核指南发现虚拟商品和数字内容必须走IAP这是平台的红线没得商量。最后老老实实接入了StoreKit为打赏功能加了IAP支付渠道。第二次是因为截图合规。App的营销截图里包含了一个模拟的“余额提现”界面审核团队认为可能引起用户误解判定为误导内容。那次让我意识到截图不是你想怎么展示就怎么展示的它必须真实反映App的实际功能任何“美化过度”都可能引来麻烦。第三次比较特殊App因为“数据收集声明不充分”被拒。我们明明没有收集某些权限数据但隐私清单里没有写清楚。后来我们仔细阅读了苹果的数据收集指南把隐私营养标签逐项对照着修改才通过。被拒不可怕可怕的是跟审核团队硬刚。我的经验是审核变更单里的每一个问题都要逐条回复能解决问题的就给录屏能提供复现步骤的就提供步骤态度要诚恳别带情绪。多被拒几次之后你会发现审核规则既是门槛也是产品合规的保护区——它逼着你在上架前把隐私、支付、内容安全这些问题都想清楚反而省掉了很多后患。5. 如果让我重新带一个新人我会让他先放下Xcode5.1 为什么先从C和内存模型学起我带过不少新人发现一个共同问题很多人一上来就抱着Xcode玩SwiftUI写了一个漂亮的界面很高兴以为自己会iOS开发了。但等到项目一复杂遇到循环引用、数据竞争、内存泄漏就完全抓瞎。如果让我重新带一个人我会让他先学C语言和内存模型。为什么因为iOS底层是Objective-C和C的运行时。不懂指针就理解不了weak和strong的本质区别不懂栈和堆就理解不了值类型和引用类型为何会有完全不同的复制行为不懂内存布局就完全看不懂Xcode的Memory Graph Debugger里那些箭头是什么意思。学完C再去理解Objective-C的面向对象、消息传递、runtime机制然后再回到Swift用Swift的高级语法去实现以前用C手动管理的事情。这条路看似绕远但走完之后你对iOS整个体系的掌握程度会远超那些只会用框架的人。很多面试题比如“循环引用怎么产生的”“weak是怎么实现的”“为什么Swift的String是值类型”在你理解了底层原理之后根本不需要背答案。5.2 多线程、网络、性能这三门必修课语言和内存之外第二个阶段我建议新人在三个方面重点突破多线程、网络层、性能调试。多线程是iOS开发里最容易出问题的地方。GCD看似简单但dispatch_async嵌套层级一多死锁、数据竞争、线程爆炸就会接踵而来。新人要先弄懂队列、任务、串行与并行的关系再学OperationQueue如何管理依赖和取消最后进阶到Swift Concurrency的Actor和async/await模型理解“隔离域”和“可重入”这些概念。网络层的基础是URLSession。要理解HTTP请求的生命周期、缓存策略、Cookie管理、断点续传还要学会处理弱网。很多新人在开发环境网络好一上线就翻车就是因为没有用Xcode的Network Link Conditioner模拟过2G、3G网络。网络层不是“能用”就行而是要设计好超时、重试、错误降级这些机制。性能调试更是一门手艺。要熟练使用Instruments里的Time Profiler、Allocations、Leaks、Core Animation这几大工具要会看Main Thread Checker报出的主线程卡顿警告要理解离屏渲染、图层混合、光栅化这些概念对帧率的影响。没有性能意识的人写的页面线上用户都觉得卡但自己开发时永远发现不了问题。5.3 十年后我还在坚持的四个习惯写代码写了十年我保留下来几个一直没丢的习惯。它们不一定能让你短时间变强但长期坚持会拉开人和人之间的差距。第一个是读苹果官方文档和头文件。很多问题在Stack Overflow上搜答案很快但只有读原文才能理解设计者的意图。我每学一个新框架都会先把官方文档的“OverView”和“Topics”部分通读一遍建立整体认知再去搜具体用法。第二个是画时序图。不管是自己设计模块还是排查线上问题我都会用纸笔画一画对象之间的交互时序。这个习惯帮助我在大项目里定位问题时快速排除干扰项也让我在设计接口时提前发现循环依赖。第三个是写技术笔记。从2015年开始我每解决一个难题就写一篇笔记记录问题现象、排查过程、根因和修复方案。这些笔记后来成了我带新人的最好素材也是我写这篇回顾的源头。第四个是定期重构“丑代码”。很多人写完能用就不动了但代码是会腐烂的。每隔一段时间我会挑自己负责的模块里最丑的一个函数把它重构成更清晰的结构。这种重构不改变功能只改善可读性和可维护性但它让我保持对代码质量的敏感度。写到这里我回头看了一眼这个标题——Ioser 铭。十年过去我早就不是当年那个对着编译进度条发愁的毛头小子了。但那个“loser”的心态我反而一直有意地保留着永远觉得自己写得还不够好永远觉得下一个问题可能还会让我通宵永远对技术保持敬畏。iOS开发这十年教会我的不是怎么记住所有API而是怎么面对“不断变化”这件事。语言会变框架会变平台格局也会变但有一些东西是穿越周期的对用户价值的理解、对系统机制的尊重、对自己能力边界的诚实判断以及写完一段优秀代码时那种发自内心的踏实感。“铭”这个字通常让人想到刻在石头上的碑文。但我不打算把这十年立成碑——它更像一个路标立在一条还没走完的路上提醒我“你看那个曾经的loser已经走了这么远。前面还有更远的继续走。”如果你也在iOS开发的路上无论你是刚入行一年还是已经写了五年希望这篇回顾能给你一点参照。你踩的坑前辈大多踩过你焦虑的问题也有人在同样深夜面对过。别慌把眼前这一个问题解决了然后接着往前走就好。
返回列表