iOS开发十年:从Objective-C到SwiftUI的踩坑与成长实录
2026/9/24 15:40:38 网站建设 项目流程

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这个名字,是我给自己立的flag

1.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到SwiftUI

2.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 Clips;iOS 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和Actor,Swift并发模型开始成熟,到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 Architecture(TCA)这些更重型的架构陆续进入视野。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,指向的是主线程上的一个系统库地址。最恶心的是,这种崩溃不发生在开发环境,只在用户手机上偶现,非常难复现。

我当时的排查链路是这样的:

  1. 先想办法拿到完整崩溃日志。通过友盟和Firebase Crashlytics收集到的堆栈显示,崩溃发生在KVO回调触发时,但当前栈里看不到任何业务代码,典型的“野指针访问”特征。
  2. 使用Instruments的Zombies工具来复现。方法是在Edit Scheme里启用僵尸对象检测,然后在模拟器上按用户描述的操作路径一步步走。果然,在某个页面销毁后再触发一次下拉刷新时,Zombies报告了一个过度释放的访问。
  3. 顺着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. 如果让我重新带一个新人,我会让他先放下Xcode

5.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开发的路上,无论你是刚入行一年,还是已经写了五年,希望这篇回顾能给你一点参照。你踩的坑前辈大多踩过,你焦虑的问题也有人在同样深夜面对过。别慌,把眼前这一个问题解决了,然后接着往前走,就好。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询