一个 Java 开发者的 iOS 入门指北

武汉的九月中旬,虽已入秋,树叶也渐黄了,但天气还是热的。夏天的夜晚依旧漫长且乏味,我已记不清楚是什么动机 —— 可能是闲得无聊,或者是偶然间在 B 站看到有人分享开发 SwiftUI 的视频觉得好玩,便阴差阳错的开始啃一本《iOS 与 Cocoa 开发指南》。

苹果的东西总是有一种魔力,让用户觉得自己“很行”:摸索着打开 Xcode,鼠标点几下就创建好了一个示例 SwiftUI 程序,没有 StoryBoard,没有 XML,类似 React/Flutter 的单文件中的声明式 UI 写法很有表现力,并且还有预览画布可供实时反馈。iPhone 连上线,点一下运行就能把程序装在手机里,看着自己的“构建成果”可能没有人是不开心的,这不可谓对新手没有“魔力”。

于是我先花了几天跟着官方文档熟悉了 Swift 的语法,又花了几天跟着 SwiftUI 的文档大致摸清了框架结构,见官网有特性丰富,展现精美的 SwiftUI 教程,便跟着教程开始一步一步的做。说实话,Apple 的魔力更多是吸引开发者,而对于技能教学,说糟糕透顶一点也不为过。这个教程我花了一周的时间才啃完,涉及的内容包括 SwiftUI 界面、SwiftUI × UIKit 交互、UserDefault、Combine 响应式编程(或者按照前端的话来说,叫做状态管理)、MapKit & CoreLocation、混合多平台开发,但总是雾里看花,懵懵懂懂并不真的理解,以至于当我试图将这些技术应用到自己的代码中时,等着我的总是无尽的编译警告和错误。至于我真正理解背后的框架和最佳实践,则是一个月之后的事情了。

在这一个月里,我跟着 raywenderlich.com 上的 Learn Path 从头开始学完了几门课程,除了跳过的 Sketch、Git、Bash 以及 Swift 设计模式和留作以后补充的 UIKit 以外,其余课程全部看完并且自己跟着做了一遍,也完成了挑战(并没有什么难度)。也读了几本网站上提供的一些教材,比如 xxx Apprentice。这些内容大体覆盖了常用的 iOS 开发所需要的技术,包括 应用设计(Figma)、面向测试的开发流程、Swift(LLVM 上的 Kotlin & Scala)、SwiftUI 控件(另一种 Flutter or React)、SwiftUI 动画、SwiftUI 布局、文件和数据(另一种 Java I/O)、URLSession 网络请求、上传和背景下载(另一种 OKHttp)、Core Location、Core Data(另一种 JPA & SQL)、Swift 异步(另一种 Akka)、GCD & Operations(另一种 Java ThreadPool)、Combine(另一种 RxJava,另一种 Flutter Provider or React Redux)。

之前我一致认为客户端技术就是画界面,属于“大前端”的一部分,而真正接触后,发现其更像是“前端”界面和“后端”服务的结合体。客户端的思路和前端网页也很不同,和界面绑定的数据更多来自于本地数据库,而网络则发生在本地数据库和远端进行的同步上。

现在网上普遍存在这样的言论:“移动互联网”的热潮早已退去,在互联网巨头市值腰斩的今天,客户端技术早已不再吃香,学完即失业,需要承认,这种观点并不无道理。PWA、各种平台的小程序、甚至是 Flutter 这类跨平台客户端技术更加便宜,开发效率更高,虽然用户体验差,且上限很低,但也能够满足很多业务的展示和简单交互需求。而反观 Native 技术,开发周期动搁小半年,流水至少十万,在经济下行的今天,又有多少人或者谁会愿意投资为用户体验买单呢?

这要分情况来看。对于大公司来说,其几乎占据了手机里常用 App 的半壁江山,其中大部分都是基于 Native 混合跨平台技术(Flutter、RN)开发的,这些公司总是需要 Native 技术的维护和特性开发,就这一点来说,虽然蛋糕不大,但也并没有说 Native 被替代。而对于中小开发者而言,在已经面临大公司垄断的情况下,使用跨端技术虽然能节省开发成本,但仅限于不需要过多原生平台能力的增删改查业务,且体验很差:蛋糕不大、能力不足、体验一般的应用程序为什么会该有市场呢?—— 除了通过行政或招标垄断强制客户使用的 toB 业务,而一旦客户能够摆脱这些业务,这些 App 绝对是第一个被卸载的那种。相反,不使用跨端技术就意味着低效吗?并不尽然,Native 的最大缺点是和平台绑定,开发效率不见得低。跨端技术的高效开发,在我看来,更多的是应对“中华田园敏捷开发” 而生的一种伪需求,应用追求大而全,庞杂且缓慢,以求提升用户留存率。在这种目标导向下,快速上线是痛点,界面一把梭怼上去完事最好。如果一个新功能是有益于客户的,是客户需要的,那么并不需要使用“灵活”但低效的跨端技术来强制动态变更界面以强迫用户使用,我相信客户是愿意为了其需要的新功能动手点击一下“应用更新”按钮的。

这里并没有谁对谁错的问题,本质是一个目的的差别:大公司的 App 的用途是作为业务入口,本身是不直接盈利的,用户被强迫使用这些功能来作为业务流程的一环,用户在这里充当的是“生产资料”,而独立 App 则是为用户服务的,用户通过一次性或订阅付费后,App 的功能就应该想用户之所想,帮助用户达成其使用目标,用户在这里充当的是“消费者”。如果将这两种目标区分开,再来讨论 Native 技术的前景,就很清楚了。对于充当“生产资料”的用户为目标的 App,大厂需要原生开发以支持增删改查之外和平台绑定的高级 Native 功能,对于独立 App,事先通过 Figma 等进行原型设计和讨论后,就单纯实现而言,Native 相比较跨端技术,开发效率并没有什么太大的区别,胜在平台能力和用户体验,弱在简单业务的跨端一致性,总体来说更胜一筹。

更何况,这两种需要原生开发的目标并不是割裂的,Native 技术提供了平台相关能力,保证了在极端情况下的用户体验,而跨端技术(Flutter 或 Web)可以无缝嵌套在 Native App 中,以最大化开发效率并节约人力成本。熟悉 Flutter 但略微了解原生的人绝对不如熟悉原生且了解 Flutter 的人来的吃香,底层平台决定上层建筑,一个再怎么华丽多变的衣裳也不如坚实可靠的地基耐操,且面对暴风雨时有用。

话说回来,现代的软件开发,基本上都已经脱离了单平台、单语言的传统模式,开始走向多语言、多范式混合开发,以尽可能的享有更多好处 —— 缺点是更陡峭的入门曲线。比如 Swift 就是一个高级版的 Kotlin & Scala 混合体,iOS 开发中大量混用 Swift 和 ObjectC 库代码(虽然接口都是 Swift 暴露的)。因此,原生技术也绝对是客户端开发深入所不得不面临的一道坎。

不懂 Java、JVM 的 Clojure 开发者注定是浅陋的,不懂 Swift、LLVM 的 Flutter 开发者也注定走不远。技术栈的底层有多牢靠,其潜力就有多大:跨端技术保证了开发效率,决定了下限,而底层技术则保证了运行效率,决定了上限。

对于我个人而言,后端业务总是需要一个展示和交互的入口,基于 JVM 和 Maven 的 Clojure 和依赖 NPM 生态的 ClojureScript 虽好,但终归受到浏览器平台的各种限制,且在移动平台表现不佳,不能有效利用原生平台能力。因此我逐渐接触了 Flutter 和 ClojureDart,并且将前端界面和组件逐步移植到 Flutter 客户端上。Flutter 的开发效率不差,且界面表现能力足够,但对于落后主流两三代的移动芯片支持不佳,卡顿时有发生(尤其在 iOS 上,Android 体验倒是不错)。自从 WWDC18 引入的通知中心 Widget 后,我一直想要把自己日常关注的内容放在 Widget 中,就像老的 Nokia S40/S60 桌面小组件那样,终于在五年后实现了自己当初的目标:CyberMe 后端基于 Clojure 开发,并且有一个 ClojureScript 实现的前端界面,之前有一个同名的 Flutter App,而现在则有了一个原生的 iOS App 和它的 Widget。

基于原生平台的能力,我可以实现:① 后台定期刷新以摆脱 Microsoft TODO App 被杀死后 TODO 事项不更新的问题; ② 早上显示今天和昨天的温差、晚上显示今天和明天的温差以帮助进行穿衣决策,每小时更新降雨信息以备安排行程; ③ 每天上下班时分提醒打卡并显示工作时长; ④ 每天提醒给植物浇水; ⑤ 从 Microsoft TODO 中通过浏览器插件自动填写禅道日报并提醒填写。

总而言之,CyberMe 提供了一套工作流,从 Micrisoft TODO 待办事项出发,待办条目可以导向禅道日报,可以导向每周计划,可以导向一个攒钱、减肥的目标积分,这三者会关联每日日记,提醒总结、记录和反思。此外,CyberMe 还提供了磁盘文件、Calibre 书籍(关联 Microsoft OneDrive)、短链接(关联 go.mazhangjing.com 书签)、物品管理能力,这些外部资源是以供 TODO 待办事项学习和分解的原材料 —— 一句话来说,CyberMe 是我的“个人持续改进”平台。

最后,虽然我还是会以 Clojure 为基准开发后端(Clojure)、前端(ClojureScript)和客户端(ClojureDart)程序,但 Swift 无疑是补上了我客户端底层的一块重要拼图。这样,在需要原生平台能力(小组件、应用菜单、背景刷新、SiriKit、地理栅栏)的时候,在需要保证用户体验、安装包尽可能小的时候,就能够快速切换到原生而不用绑死到 Flutter 这个纯 GUI 的 Canvas 上。对于前端、后端和客户端而言,客户端对用户体验是最敏感的,也是最可能进行原生/跨端转换的。如果说我对 Swift 这门语言有什么意见的话,编译并不快(这其实是一种选择 —— 在语法糖和编译速度之间的选择),对于 iOS 平台有什么意见的话,不开源,框架思路设计奇特,特立独行可能是我最不喜欢的部分了(尤其是 UIKit 和 Core Data),不过这些大体属于遗留问题,最近的一些框架(SwfitUI、Combine)的体验还是很不错的,技术也都很先进、易学易用。

以上。