成也UNI,败也UNI
在不久的以前,写代码还需要大量人力的时候,跨平台框架无疑是最好的选择。不管是uni还是flutter,都是为了这个而生的,当然这不是最早的跨平台框架,在这之前还有很多其他的框架,可能大家都不曾听说过,我简单介绍个我用过的两个:b4a(basic for andorid),我最开始并不怎么喜欢写(再问就是不会)安卓的java代码,想着偷懒,不要去弄安卓那一套,偷懒玩了下b4a,这也是最开始做findu的时候最朴素的想法;另外一个是delphi的firemonkey框架,这个东西体验的更早,刚来青岛的时候,为了查询公交基于这个东西做了个公交查询的app,最后跑在了ios上。
这些框架,不能说生不逢时,只能说基础功能还是稍微有些欠缺,生态不完整,例如地图、Im等组件的支撑缺失,这也是最终为什么直接采用原生oc和java开发了最早版本的findu。
然而,随着ai的发展,跨平台已经不再是一个主要的投入成本了。借助ai的能力可以快速复刻一个app到其他的平台,以前是点子不值钱,现在技术也不值钱了。跨平台唯一的好处仅剩下代码的可维护性,一套代码通吃各个平台,相对来说代码维护和bug修复的一致性会比多平台更加便捷。
可能这个发展偶尔也会有人会高喊暂停:
2026 年 8 月 11 日,美国参议员伯尼·桑德斯(Bernie Sanders)日前致信 OpenAI CEO Sam Altman、Anthropic CEO Dario Amodei 和 Meta CEO Mark Zuckerberg,要求三家公司暂停 AI 开发,并停止继续构建人类无法控制的 AI 系统。
至于暂停或者不暂停,现有的技术能力已经足够解决日常的技术问题和满足开发需求。也基于这种现有的技术能力,最近重新重构了findu,开发了新的portable claw。这里我想说的就是这个portable claw,借鉴之前的开发经验,想着一套代码解决所有问题,采用的架构依然是uniapp,不过不是uniappx。
按照uniapp 官方文档的说法,x其实是下一代,能达到原生的性能。我在开始的时候错误的评估了app在ios上对于性能的消耗,并且初期测试的时候主要也是基于ios模拟器测试,真机测试相对来说比较少,数据量也没有达到日常使用账号的数据量。导致对于app在ios真机上的性能过于乐观,数据量大了之后,会话窗口的绘制就成了一件痛苦的事情。
uni-app 在 iOS 真机上的瓶颈,不只是「聊天列表用了 WebView」。换成 nvue,只是把聊天 UI 从 WKWebView 换成 Weex 原生控件,业务、流式更新、页面栈、桥接仍在 JS 容器里。所以能减轻长列表掉帧,但到不了 Swift + UITableView 那种原生体验。
uni-app 在 iOS 真机上真正卡在哪
1. 双运行时一直同时活着
本项目 iOS 不是「整 App 都是 nvue」:
- 聊天:
pages/chat/ios.nvue(Weex) - 状态 / 设置 / 登录 / 订阅:仍是 Vue + WKWebView
- 业务宿主:
stores/iosChatHost.js挂在App.vue的 JS 服务层
chatRoutes.js 里写得很清楚:Vue 宿主和聊天 nvue 作为 subNVue 同时常驻,Tab 只挪 overlay。真机上等于:
- 一套 WKWebView(设置等)
- 一套 Weex(聊天)
- 一套 5+ Runtime / Pandora
内存、启动、Tab 切换都比单进程 UIKit 贵。Android 用 index.vue 还能凑合,iOS 对 WebView 和后台 WebContent 更狠,真机掉帧、杀进程都更明显。
2. 主路径仍是 JS:解析、裁剪、再塞回 UI
Gateway / Hermes 事件、历史、tool 卡、附件、TTS、权限,全部在 JS 里做。nvue 不能 import gatewayMedia、@noble/* 等,否则 Weex 白屏。于是每条流式更新都是:
JS 宿主算完 → cloneMessage 裁成短摘要 → uni.$emit 整份快照 → nvue 再 v-for 刷 <list>
原生工程则是:SSE 回调直接改 ChatMessage,UITableView 按行刷新。中间没有「整窗 JSON 快照」。
3. 聊天数据本身就重
长会话卡、空会话不卡,文档里已经定性:推给 nvue 的是「当前窗口」的完整 JSON,最后十几条常带着大段 tool 输出和 data: 图。你们被迫:
- 窗口大约 16 条,上限约 48
- 正文截断、不传
toolCards、不传data:预览 - 流式尽量
splice一条,避免整表替换
这是在 迁就 Weex 序列化 + 布局,不是产品想要的完整列表。原生 ChatTableView 可以按 cell 复用、按行测高,不必先把历史砍成摘要。
4. 5+ / Weex 桥是同步税
例如内购注释:不要和 StoreKit 弹窗同时触发 Weex 布局,否则会 同步 调 getSafeAreaInsets 卡住。真机上 plus.*、相册、文件、支付、安全区,都会在 JS 线程和主线程之间来回。原生是直接调系统 API。
5. Weex 不是 UIKit
<list> / <cell> 看起来像原生列表,实际是:
- JS 虚拟 DOM → Weex 原生节点
- 能力子集(布局、富文本、手势冒泡都弱)
- 本页还用了 list-flip 倒序列表 才能贴底,属于 Weex 滚动模型的绕路
- Markdown、代码块、动态高度,Weex 测高和复用远不如
UITableView.automaticDimension
nvue 缓解了什么,为什么仍不够
nvue 确实缓解了「用 webview <scroll-view> 画几百条气泡」这一层:原生 view 绘制、有限回收、少一次 DOM/CSS。
它没有拿掉下面这些,所以仍然不是原生 App:
| 还在的成本 | 说明 |
|---|---|
|
双引擎
|
聊天 Weex + 其它页 WebView,Tab 常驻两套页面
|
|
快照协议
|
宿主和 nvue 用事件传 JSON,流式时反复 clone / emit / diff
|
|
瘦 UI
|
重逻辑不能进 Weex,热点路径仍在 JS
|
|
列表策略
|
必须截断、限窗,完整历史会把
<list> 打爆 |
|
容器
|
DCloud 基座、模块、plus、描述文件,启动和权限都比单 target Swift App 重
|
|
表达力
|
做不到原生那种 cell 精细复用、TextKit/AttributedString、键盘 inset、分屏
|
所以:nvue 是「聊天绘制层换引擎」,不是「App 换成原生」。 瓶颈从「WebView 排版」变成「JS 宿主 + 跨运行时同步 + Weex 列表模型」。对 Agent 聊天这种 高频流式、变高 cell、大 tool 输出 的场景,第二套瓶颈一样会被打满。
和原生工程差在哪(同一产品)
原生 native_ios 是单一 Swift 进程:UITableView + 按条 merge、键盘按窗口算 inset、没有 uni.$emit 快照窗。uni-app 即使用 nvue,也还是「JS 算完再通知另一套 UI」。
一句话: nvue 能让短会话、轻气泡比纯 Vue 页跟手;真机长会话、流式、工具卡、多 Tab 同时在时,瓶颈在运行时架构,不在某一两个 CSS。要到原生手感,只能像现在这样离开 Weex/WebView,用 UIKit/SwiftUI 自己画列表。
以上内容是基于ai的总结,基本也就是这个情况,尤其是对于这种可变cell+列表的绘制,性能就更是问题了,哪怕独立出来nvue页面,js主进程还是会卡住其他的操作。
相反,在andorid设备上就不会存在这个性能瓶颈,虽然都是uniapp框架,在安卓设备上流畅度基本能达到原生的效果。
实际真机效果对比:
上面的视频是 进入app之后,首先点了状态,后续其实又点了其他的tab页,但是此时已经卡死了。
上面的视频是基于swift的ios原生项目。
虽然页面功能一样,但是绘制的效果和性能却是天差地别。
当然,uni框架还有另外一个缺点就是,一些与系统打交道的东西还是需要原生插件支持,例如ios 的定位回调,ios的healthkit(闺蜜圈APP),安卓的各种系统设置等等,多数都是基于现有的uni插件来实现,所以,findu虽然是页面样式基础功能跨平台了,底层依然是基于原生的定位组件,系统操作组件,以及badge组件来堆起来了app的核心功能。
至于portable claw,则是需要tts引擎,很不幸baidu的tts引擎超过了200m,导致无法使用uni的云打包(打包也可以,交钱就行)。
这个问题,其实我之前在其他的文章中也提过。
为了解决云打包的问题,我是用uni的本地打包,基于xcode项目来实现打包,这也是ios版本发布的必经流程,这个操作其实也不算简单,需要配置一系列的东西,包括证书 mp文件等等。
这一系列的操作都是为了能够复用uni的代码,不用重新写。
只是,现实狠狠地给了我一巴掌,告诉我这个东西虽然可行,但是不够完美,或者说达不到自己的心理预期。作为一款app,这个运行效率是无法忍受的,这就有了原生的swift的ios项目:
说了这么多,并不是说uniapp一无是处。
在不需要与系统底层交互app中,uni无疑依然是开发效率和跨平台的最优选择,继承了基础的三方登录、内购、分享、地图等等能力,这些能力对于多终端的适配的确可以减少大量重复的工作。
不过,如果你需要一个高性能的app,那么最好的办法就是直接原生开发(有了ai,一切都简单了),至少目前我这里的优化已经到了自己的能力极限了。
成也UNI,败也UNI。
附简要跨平台框架性能对比(基于cursor总结)仅做参考: