Android 老手开始跑 iOS:一份 106 周的双线迁移笔记

2 阅读10分钟

写到 Android 第 13 周那个周末,我把 SavedStateHandle 那节文章发出去,关掉电脑,坐在椅子上愣了一会儿。

倒不是写得多好,主要是忽然意识到一件事:我在 Android 这边已经积累出来的东西——Fragment 生命周期、RecyclerView 复用、SavedStateHandle 双存活级别、TaskStackBuilder——如果只停在 Android 端,3 年后我大概率会变成「Android 工程师」而不是「移动端工程师」。而我心里真正想成为的,是后者。

所以那周结束的时候,我做了一个决定:在 Android 106 周规划继续跑的前提下,同时启动一条 iOS 106 周规划。两份规划周主题尽量对齐(UILabel ↔ TextView、UITableView ↔ RecyclerView、UIViewController ↔ Activity+Fragment、URLSession ↔ OkHttp、Codable ↔ Moshi/Gson),同周两个端各做一个 Demo、各写一篇文章、各留一份记录。

代价是 106 × 2 = 212 周的训练时长。这个数字挺吓人的,但它换来的不是「我同时会两个平台」那么简单——是「任何一个端的能力都能在另一个端找到对应物,反过来也成立」。这是双线同构最大的复利。

但我得先说清楚一件事:iOS 的第 1 周我其实在前两周就跑完了,是因为迁移路径太熟悉了——UILabel 的七大功能我能在脑子里直接映射到 Android TextView,跑马灯一查发现没有,三十分钟就决定手写,写完出来才意识到这正是 Android 那边没机会讲的「滚动原理」。这条线已经动了。下面是我对这条线未来 105 周的盘算。

这条线不是「再来一遍」,而是「补一份 iOS 视角」

Android 我已经写到第 13 周,对 View 层、四大组件、协程、ViewModel 这套体系算是有了手感。但 iOS 这边的几个底层决策点,Android 老手带过来的肌肉记忆会反过来误导——这是我跑了第一周就撞上的事:

  • UIStackView 设 backgroundColor 不显示。 它是个不参与绘制的容器,背景色必须垫一层真实的 UIView。我在 Android 那边用 LinearLayout 设背景色是直觉反应,到了 iOS 就直接掉坑。这种「名字像、行为完全不同」的地方,迁移路上全是。
  • UILabel.numberOfLines 默认 1——TextView 默认多行,刚好反过来。新手坑的概率在迁移人群里反而比原生 iOS 新手更高。
  • iOS 没有跑马灯。 安卓 ellipsize="marquee" 加 isSelected = true 就完事,iOS 翻遍找不到。我反而觉得这是好事——跑马灯的原理(双文本循环 + 帧驱动)被安卓 API 藏起来了,iOS 逼你手写,原理反而看得清。
  • 没有 Fragment。 Android 的 Fragment 解决的是「页面组件化 + 生命周期跟着宿主走」,iOS 用 Child View Controller(addChild/willMove/didMove 三步曲)做同样的事,但 API 完全不一样。这种「同一问题、不同载体」的对照,正好是双线同构最值钱的地方。

第一周用「UILabel 全功能 + 文字渲染优化」开场,对照 Android 第 1 周的 TextView,是这套对齐方式的第一次正式演练。后面 105 周,每周都会是这样——同一个能力在两个端各走一遍,对照着看。

106 周分三段,不是按时间分,是按认知层级分

我把这条线划成 3 个 Part,每个 Part 内部也是按认知跃迁分的,不是按"够不够 20 周"分出来的。

Part 1:基础与架构(第 1-40 周) —— 把 iOS 当成一个跟 Android 同等重要的工程体系来建地基。UIKit 视图体系(UILabel/UITextField/UIButton/UIImageView/UITableView/UIAlertController)→ 视图控制器与导航(UIViewController/UINavigationController/UITabBarController)→ 滚动与集合(UIScrollView/UICollectionView/Compositional Layout)→ 布局(Auto Layout/UIStackView)→ 手势与动画(UIGestureRecognizer/Core Animation/UIViewPropertyAnimator)→ 架构与通信(MVC→MVVM/委托/闭包/通知/KVO)→ 数据层(UserDefaults/Keychain/Codable/URLSession)→ 并发(GCD/Operation/async/await/Actor/Combine)→ 系统机制(RunLoop/ARC/Core Data/SwiftData)→ 系统能力(AVFoundation/相机/定位/推送)。

这 40 周结束的时候我应该能回答这些问题:一个页面从 UI 到数据流怎么组织?列表为什么有时稳有时掉帧?架构演进从 MVC 到 MVVM 的判断时机在哪?这些是判断能力,不是 API 记忆。

Part 2:工程质量与进阶(第 41-80 周) —— 把代码从「能跑」带到「能长期活下去」。SwiftUI 整段(视图/@State/@Observable/NavigationStack/动画/混编)、测试(XCTest 单元测试/XCUITest UI 测试/快照测试/替身与依赖注入)、CI/CD(Fastlane/Xcode Cloud/签名与 TestFlight)、安全(Keychain 高级用法/Face ID/Touch ID/网络安全与 ATS)、高级 Swift(协议泛型深入/内存布局与指针/启动优化 pre-main→main/包体积优化/崩溃治理/卡顿监控)、APM(Instruments 全家桶/MetricKit/os_signpost)、构建(Swift Package Manager 模块化/编译耗时优化)、架构演进(组件化与路由/TCA)、跨平台探索(KMP/Flutter 对照/容器化)、端侧 AI(Core ML/Create ML/Vision/NaturalLanguage)、发布变现(App Store 审核/IAP/订阅)。

这一段的目标不是"知道更多 API",是「能在团队里讲清楚一个 App 为什么这样设计、为什么这样构建、为什么这样监控」。从「写代码」到「做工程」的跃迁点就在这里。

Part 3:专项技术与深入(第 81-106 周) —— 真正把 iOS 当成一个完整生态来用,而不是另一个手机平台。无障碍(VoiceOver/动态字体/对比度)→ 多尺寸适配(iPad/Mac Catalyst/visionOS)→ 设备能力(CoreNFC/CoreBluetooth)→ 系统级扩展(WidgetKit 小组件/画中画/App Groups)→ 多端平台(watchOS/CarPlay)→ AR(ARKit/RealityKit)→ 内存与诊断(Leaks/Allocations/Jetsam 日志/os_log + 日志回捞)→ 体验进阶(启动规范/复杂转场)→ 系统底层源码(objc4 开源仓库逐行精读 objc_msgSend/Method Swizzling/关联对象/autoreleasepool/weak 表;CFRunLoop 源码逐行精读;dyld 与 Mach-O 加载流程;CoreFoundation 与 Foundation 桥接)→ Web 容器(WKWebView/WKScriptMessageHandler/evaluateJavaScript/JSBridge/离线包/WKURLSchemeHandler/跨端容器实战)→ 毕业整合。

第 95-100 周那六周是整条线我最期待的部分。安卓那边有 Framework 层可以读,iOS 这边的源码深挖走的是另一条路——objc4 开源部分(消息发送、引用计数、weak 表)足够把"Objective-C 这套运行时是怎么工作的"翻个底朝天;CFRunLoop 是 Apple 部分开源的 CoreFoundation 关键模块,逐行精读一遍比读 100 篇博客都管用;dyld 和 Mach-O 闭源,但 Apple 在 opensource.apple.com 开源的 dyld 仓库可以自己编译出 dyld_shared_cache_util 和 dyld_info,加上 otool(Xcode 命令行工具自带)和开源 GUI 工具 MachOView 足够把启动流程拆开讲清楚。两端的源码深挖走的是两条不同的路,最后拼出来的是一个完整的「现代操作系统是如何把字节码跑起来」的图景——这是双线同构在最深处的价值。

三个我自己也没完全想清楚的事

写到这里我得诚实交代:这条线有相当一部分我自己也还没想清楚。

一是「双线同步」到底是什么程度的同步。 周主题 1:1 是定的,但「同一周里安卓端先做还是 iOS 端先做」还没定。可能大多数时候两边几乎同步推进(同一周各交付),但有些周 iOS 端会先做完,有些周安卓端会先做完——尤其是源码周,iOS 的 objc4 精读可能跟安卓的 FrameWork 源码精读不在同一个心智状态,硬同步反而违背节奏。这个我想边走边看。

二是 Windows 上没法跑 Xcode 这件事怎么处理。 我现在的工作机是 Windows。iOS 工程需要 macOS + Xcode 才能编译运行。我目前的做法是:交付代码级产物(手写 project.pbxproj、Swift 源码按 API 正确性自检),Mac 上 Xcode 15+ 打开直接跑;构建验证留给 Mac 环境的间歇。这不影响文章和记录,但意味着 iOS 端的"可运行 Demo"在 Windows 周期内是"代码级验收",不是"截图级验收"。等以后哪天去 Mac 上一遍跑完,再回来补差异。

三是 SwiftUI 起点放在第 41 周是不是保守过头。 苹果已经明确 SwiftUI 是未来,新项目里 SwiftUI 用得越来越普遍。但我决定第 1-40 周坚持 UIKit 纯代码布局(NSLayoutAnchor,不引三方库),跟安卓那边「传统 View + XML 起手、Compose 后置」的节奏保持一致——这样双线在每个阶段做的是「同等认知难度」的事,跨平台对照才有意义。如果哪天发现 SwiftUI 在某个组件上已经比 UIKit 成熟得多,我会提前,不死守 41 周。

这三个问题不是"计划不完整",是"计划在按真实节奏走"的样子。一份把所有事都钉死的规划,反而不像是真打算执行的。

接下来 13 周我打算这么过

先把 Android 已经走过的 1-13 周在 iOS 这边追平。具体节奏:

  • 第 1 周 UILabel 全功能 + 文字渲染优化(✅ 已完成,文章已发,跑马灯 + 渲染优化三件套已交付)
  • 第 2 周 UITextField/UITextView 全功能 + 输入体验优化
  • 第 3 周 按钮组件全功能 + 点击优化
  • 第 4 周 UIImageView 全功能 + 图片加载优化(触发图片加载框架专题,Kingfisher/SDWebImage/Nuke 三选一会进入专题深入任务池)
  • 第 5 周 Asset Catalog + 深浅色 + 资源优化
  • 第 6-7 周 UITableView 基础/高级 + 列表硬核优化
  • 第 8 周 弹窗提示组件 + 弹窗优化
  • 第 9-13 周 UIViewController 生命周期 / 跳转传值 / 导航栈 / 状态保存

跑完这 13 周,就相当于 iOS 端"补完了 Android 那边已经走过的地基",然后两条线真正开始并行走。这个月就能走到第 2 周 UITextField,输入框是两端的共同主题里最容易做对照的一个——EditText 的输入防抖、KeyboardType 的限制、returnKeyType 的链式跳转,对面是 UITextFieldDelegate 的 shouldChangeCharactersIn + KeyboardType + 焦点链。下周那篇文章我会专门写一节讲双端"输入体验优化"的对照——比如 Android 的软键盘监听 vs iOS 的 keyboardWillChangeFrameNotification,名字不一样,处理时机和坐标系都不同,但都在解决"键盘弹起来把输入框挡住"这件事。

iOS 第 1 周那篇 UILabel 文章里我专门写了一节讲"为什么 iOS 没有 marquee 不是缺陷"——这种「名字像、行为完全不同」的事如果不正面写下来,迁移心态会越来越像在做两份工作而不是一份工作。每周文章末尾我会固定留一节「安卓老手视角」,专门写 iOS 端撞上的反直觉时刻。这是我打算长期维持的写作纪律。

一个不太像收尾的结尾

写到第 105 周的时候我大概会变成什么样,我也不知道。

大概率 Android 那边跑过了 AOSP/AI 原生手机的源码巅峰,iOS 那边也跑过了 objc4/RunLoop/dyld 的逐行精读。两端对照下来,应该能回答一些现在回答不了的问题:MVVM 在两端的最优形态分别是什么?两端性能瓶颈的根因是不是有家族相似性?两端的"系统级能力"(iOS 的 extension 体系 vs Android 的 service/系统应用)在架构上是不是同一类东西?

也可能最后发现双线同构并没有想象中那么美——有些对照是强行对照,两端的差异是本质性的,硬映射反而误导。这种结论也是结果。

但无论哪种,写下来就是结果本身。

第 1 周那篇 UILabel 文章已经在 article-output/02-第1周-UILabel全功能与文字渲染优化-输出文章版.md 里,工程在 ios-project/IOSStudyAll/ 下,首页点「第 1 周」卡片就能看到七个实验区。跑马灯那个组件值得盯着看一会儿——CADisplayLink 一边转,文本一边滚,第 22 周讲 Core Animation 时这些全是底子。

下一篇文章,下周见。