我对技术探索与实践的看法:一个老iOSer的“折腾”手记
大家好,我是阿哲,在杭州摸爬滚打做了6年iOS开发。从Objective-C写到Swift 5,从Xcode 6熬到Xcode 15,见证了Swift从“玩具语言”一步步变成Apple生态的亲儿子。平时除了在公司搬砖(目前在一家靠近网易的中型互联网公司),我也喜欢参加GDevCon、SwiftGG meetup这些技术分享会,跟同行吹吹牛、踩踩坑。
今天想聊聊我对“技术探索”这件事的真实看法——不是那种高大上的方法论,而是我在实际项目里被逼着学新东西、踩坑、修锅、最后勉强上线的血泪史。
被逼上梁山:为什么一个iOS工程师要碰JavaScript?
去年双11前一个月,我们产品突然提了个需求:要在App内嵌一个动态活动页,支持实时配置、快速迭代,最好当天改完当天上线。产品经理原话是:“别再走发版流程了,太慢!”
我一听就头大。我们的主App是纯原生Swift写的,动态化方案一直没落地。领导一拍大腿:“要不试试Hybrid?用WebView加载H5页面,前端同事用JavaScript写逻辑,你们Native负责桥接就行。”
说实话,我心里一万只草泥马奔腾而过。我不是讨厌JavaScript——毕竟大学时也写过jQuery,但那都是十年前的事了。现在JS生态卷成啥样了?Webpack、Vite、React、Vue、TypeScript……我连npm install都怕装错依赖。
但Deadline不等人啊。双11流量高峰就在眼前,老板盯着数据看,测试同学已经在群里@我三次了:“Native这边联调环境好了吗?”
真实项目中的“缝合”实践
我们最终决定采用 WKWebView + JavaScriptBridge 的方案。核心思路很简单:Native提供能力接口(比如获取用户信息、调起支付),H5通过JS调用这些接口,实现业务闭环。
但现实远比想象骨感。
坑1:JS和Swift的数据类型对不上
前端同事传了个 { userId: 12345 },我用 evaluateJavaScript 拿到的是 Any?,强转成字典失败,直接crash。后来才发现,WKWebView返回的其实是 NSNumber,不是 Int。
// 别这么写!会崩!
if let dict = result as? [String: Int] {
let id = dict["userId"] // 💥
}
// 正确姿势:先转成基础类型
if let dict = result as? [String: Any],
let userId = dict["userId"] as? NSNumber {
let id = userId.intValue // ✅
}
坑2:JS回调时机混乱
前端调用 native.getUserInfo() 后,需要等Native异步返回结果。一开始我们用 completionHandler 直接回传,结果发现:如果JS连续调用多次,回调顺序会乱,甚至丢失。
后来参考了 WebViewJavascriptBridge 的思路,给每个调用加唯一ID,用字典缓存回调:
private var callbackMap: [String: (Any?) -> Void] = [:]
func handleJSMessage(_ message: [String: Any]) {
guard let messageId = message["id"] as? String,
let methodName = message["method"] as? String else { return }
switch methodName {
case "getUserInfo":
fetchUserInfo { userInfo in
self.sendResponse(to: messageId, data: userInfo)
}
// 先存起来,等异步完成再触发
callbackMap[messageId] = { [weak self] data in
self?.webView.evaluateJavaScript("window.handleResponse('\(messageId)', \(data ?? "null"))")
}
default:
break
}
}
前端那边也得配合:
// JS侧也要维护一个callback map
const callbacks = {};
function callNative(method, params) {
const id = Date.now().toString();
const message = { id, method, params };
window.webkit.messageHandlers.nativeHandler.postMessage(message);
return new Promise((resolve) => {
callbacks[id] = resolve;
});
}
// Native调用这个函数来触发回调
function handleResponse(id, data) {
if (callbacks[id]) {
callbacks[id](data);
delete callbacks[id];
}
}
这套机制跑通那天,我差点在工位上跳起来——终于不用每次改个文案都要等两周发版了!
技术选型:稳字当头,但不拒绝尝鲜
虽然我爱折腾新技术(私下用SwiftUI写了好几个Side Project),但在公司项目里,我始终信奉一个原则:能用稳定的,就别上实验性的。
比如这次Hybrid方案,我们没选React Native或Flutter,就是因为:
- 团队没有RN维护经验,学习成本高
- 双11期间不敢赌稳定性
- H5天然支持热更新,符合“快速迭代”需求
我把几个方案的对比拉了个表:
| 方案 | 开发效率 | 热更新 | 性能 | 团队熟悉度 | 风险 |
|---|---|---|---|---|---|
| 纯原生 | ⭐⭐ | ❌ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 低 |
| React Native | ⭐⭐⭐⭐ | ✅ | ⭐⭐⭐ | ⭐ | 高(无经验) |
| WebView + JS | ⭐⭐⭐ | ✅ | ⭐⭐ | ⭐⭐⭐ | 中(可控) |
| Flutter | ⭐⭐⭐ | 部分 | ⭐⭐⭐⭐ | ⭐ | 高 |
最终选了折中方案。事实证明,虽然性能不如原生,但在活动页这种轻交互场景下,用户体验完全可接受。
心得:探索是为了更好地“稳”
现在回头看,这次被迫接触JavaScript的经历,反而让我对跨端协作有了更深理解。以前总觉得“前端=切图仔”,现在知道他们也在处理复杂的异步状态、内存泄漏、兼容性问题——只不过报错信息从 EXC_BAD_ACCESS 变成了 Cannot read property 'xxx' of undefined。
更重要的是,我意识到:技术探索不是为了炫技,而是为了解决真实问题。如果你只是为了在简历上写“精通WebAssembly”,却连项目里的Crash率都没降下来,那纯粹是自嗨。
上周五晚上加班修复一个JS Bridge的内存泄漏时(又是闭包retain cycle!),我突然悟了:所谓“资深”,不是你会多少框架,而是你知道在什么场景下该用什么工具,并且能把坑填平。
写在最后
在杭州这座互联网重镇,阿里、网易的技术氛围确实浓厚。但我越来越觉得,比起追新,把现有技术用到极致,才是更稀缺的能力。
下次如果你看到我在GDevCon上分享《如何用Swift优雅地调用JavaScript》,别笑——这背后可能是无数个深夜debug的辛酸。
技术探索的路上,我依然会折腾,但会带着镣铐跳舞:业务需求是锁链,线上稳定性是地板,而我的目标,是在这之间跳出最优解。
毕竟,代码能跑,才是硬道理。不是吗?😉

评论 0