featured.svg

最近「Vibe Coding」這個詞在開發者社群裡吵得沸沸揚揚——與其一行一行敲鍵盤,不如用自然語言描述你想要的功能,讓 AI 幫你實現。聽起來很美好,可是這招真的能拿來開發生產級別的應用嗎?還是只是寫寫玩具專案用的呢?

我想與其空想,不如自己動手驗證一下。於是我用 Claude Code 加上 Claude 4.5 Opus,從零開始打造了一款完整的 iOS 應用程式:AlphaGPS——一個透過藍牙低功耗(BLE)把 iPhone 的 GPS 資料同步到 Sony 相機的小工具。

值得一提的是:我從來沒寫過任何 Swift 程式。雖然我以前有 Objective-C 的 iOS 開發經驗,但 Swift 和現代 iOS 那一整套框架對我來說根本是全新的領域——SwiftUI、Combine、async/await、Live Activity,這些我全都是第一次碰。

而且這實在不是個簡單的 Hello World 專案。它牽涉到 BLE 硬體協議、iOS 背景執行限制、頗複雜的狀態管理,還有 Live Activity 整合。最後的成果呢?大概 4,100 行 Swift 程式碼、30 個檔案、90 個 commits,以及一個真的能用的產品。

為什麼選擇這個專案?

身為一個攝影愛好者,我一直很希望照片能自動記下拍攝的地點。Sony 雖然有官方的 Creators App,可是它的背景同步體驗實在頗遜——三不五時就要手動重連,而且還會把我其他的藍牙連線給踢掉。

我真正想要的其實很單純:打開相機就自動連上、App 縮到背景也能持續同步、整個過程完全不用我動手。

這個需求剛好涵蓋了一堆有趣的技術挑戰,拿來測試 AI 輔助開發的極限,真是再適合不過了。

Vibe Coding 的實際體驗

第一天(1/11):從協議規格到基礎架構

第一個 commit 是 Initial commit with Sony GPS protocol spec——我先把逆向工程得到的 Sony 藍牙 GPS 協議文件丟進去,然後就直接跟 Claude 說:「根據這份協議規格,幫我設計一個 iOS App 吧。」

接下來的兩個 commits 分別是:

  • feat(phase1): implement iOS app foundation for Sony camera GPS sync
  • feat(phase2): implement BLE foundation and GPS encoding

Claude 建議使用 Multi-Manager Singleton 模式搭配 Combine 響應式框架:

「你的 App 需要協調多個系統:藍牙掃描、GPS 定位、相機連線狀態。
建議使用獨立的 Manager 單例,各自管理 @Published 狀態,
透過 Combine 讓 UI 自動響應變化。」

這可不是 Claude 隨便唬爛的建議喔——它是真的考慮到 CoreBluetooth 的 Delegate 模式和 SwiftUI 的響應式特性,照著 iOS 開發的最佳實踐給出來的。

第二到第三天(1/12-1/13):攻克背景執行

iOS 對背景執行的限制嚴格得不得了,大多數 App 一縮到背景沒多久就被系統砍掉了。可是我要的偏偏是:就算 App 整個被殺掉,相機重新開機的時候也得乖乖自動重連。

這部分有多硬,看 commit 歷史就知道了:

  • feat(phase4): implement background lifecycle and persistence
  • Implement iOS background BLE state preservation and cleanup code
  • Start scanning in background when cameras exist but none connected
  • Start scanning when last connected camera disconnects in background
  • Abort connecting cameras when app enters background
  • Stop scanning when camera connects in background

這時候就輪到 iOS Core Bluetooth State Preservation 登場了。我把需求跟 Claude 講清楚:

「我需要相機關機後再開機時自動重連,
即使使用者已經完全關閉 App。」

Claude 不只丟程式碼給我,還順便把整個機制講解了一遍:

  1. CBCentralManagerOptionRestoreIdentifierKey 初始化藍牙管理器
  2. iOS 會在背景記住連線意圖
  3. 當藍牙事件發生時,iOS 會喚醒 App 並呼叫 willRestoreState
  4. App 需要在這個回調中重建狀態並恢復連線

最難搞的還是那些邊界情況:藍牙 UUID 可能會變、相機可能同時連好幾台、狀態恢復還可能搶在位置權限授權之前發生。這些 Claude 都幫我一個一個想過,最後才真的做出那種「完全不用碰它」的體驗。

Sony 的 BLE 協議

Sony 從來沒公開過他們的藍牙 GPS 同步協議。還好我在網路上找到了其他開發者的逆向工程成果(特別是 camera-gps-link 這個專案),人家早就把協議細節摸得一清二楚。我把整理過後的協議規格放在 docs/sony-gps-protocol-spec.md

協議的關鍵在這:要寫入 GPS 資料,得先跑一個三步驟的「解鎖」序列:

  1. 寫入解鎖指令到 DD30 characteristic
  2. 寫入鎖定指令到 DD31 characteristic
  3. 寫入 GPS 封包到 DD11 characteristic(91 或 95 bytes)

只要漏掉任何一步,相機就會默默把資料吃掉當沒這回事——連個錯誤都不會回給你,超難 debug。

我把這個發現告訴 Claude 之後,它馬上就抓到了非同步寫入的眉角:

// Claude 建議的解決方案
private var unlockWriteComplete = false
private var lockWriteComplete = false

// 只有在兩個階段都完成後才發送 GPS
func checkAndWriteLocation() {
    guard unlockWriteComplete, lockWriteComplete else { return }
    // 發送 GPS 封包...
}

這種狀態追蹤的寫法,剛好解決了 BLE 寫入回調可能亂序、甚至直接漏掉的問題。

印象深刻的地方

1. 理解上下文的能力

1/13 那天,我花了不少時間在搞 Live Activity。從 commit 歷史就看得出這段路走得不太順:

  • Implement Live Activity for camera connection status
  • Fix iOS availability checks for Live Activity API
  • Fix Widget Extension bundle configuration
  • Fix ActivityViewContext build error by inlining Live Activity view

後來到了 1/16,我才發現「這個 Live Activity 斷線的時候沒有正確收掉」。Claude 沒有只是傻傻地搜關鍵字,而是把整個程式碼流程追了一遍,最後發現問題其實出在狀態轉換的時序上:

didConnect → 狀態設為 .connected → 啟動 Live Activity
    ↓
handleConnection → 狀態改為 .connecting(進行服務發現)
    ↓
didDisconnect → wasConnected 檢查回傳 false → Live Activity 沒有結束!

修好的結果就是 fix(ble): always end Live Activity on disconnect 這個 commit。AI 能理解到這種程度,除錯速度真的快了不少。

2. 主動考慮邊界情況

在實作電池優化時,Claude 主動提出:

「你需要考慮這些情況:
- App 在背景但沒有相機連線 → 應該暫停 GPS
- 相機在背景連線 → 立即恢復 GPS
- 使用者關閉背景同步設定 → 停止所有更新」

這最後變成 feat(location): stop GPS updates when no cameras connected in background 這個 commit。說真的,這些情況我根本沒明講過,可是它對產品品質實在是很關鍵呢。

3. 架構一致性

專案一路長到 30 個 Swift 檔案,Claude 卻始終記得我們一開始定下的那套模式:Manager 單例、原子性狀態更新、Combine 訂閱管理。它不會突然冒出一個風格完全不一樣的實作來嚇你。

看看下面這幾個 refactor commits,Claude 一邊幫我持續改進架構,一邊又沒把一致性給搞砸:

  • Refactor to single camera list and state-based architecture
  • Consolidate Camera discovering and connecting states
  • Refactor autoConnect to separate field from connection state
  • Consolidate connected and syncing states into single connected state
  • refactor: simplify connecting state by removing retryCount

不完美的地方

不過老實講,Vibe Coding 也不是萬能的啦。從那 90 個 commits 裡,你會看到一堆 fix commit:

Bug fixes 佔的比例真的不小

  • fix(ble): clear peripheral when aborting connection
  • fix(ble): cancel connection timeout on disconnect
  • fix(ble): fix critical disconnect handling issues
  • fix(ble): clear stale peripheral on background disconnect

這些清一色都是實機測試才抓到的。Claude 寫得出結構漂亮的程式碼,可是 BLE 跟 iOS 背景執行的邊界情況實在太多了,不真的跑一遍裝置根本發現不了。

得把話講白:Claude 偶爾會想太多、過度工程化。我得明白地跟它說「保持簡單就好,別自己加料」,它才會收斂。

測試還是很麻煩:BLE 跟 Live Activity 在模擬器裡根本測不了,每個功能我還是得乖乖在實機上一個一個手動試過。

領域知識少不了:程式碼是 Claude 幫我寫的沒錯,但要搞懂 Sony 協議、iOS 背景限制、BLE 的各種特性——這些終究得我自己去查、去讀。AI 是個超強的副駕駛,可是方向盤往哪轉,還是得你自己心裡有底才行。

成果總覽

指標 數值
程式碼行數 ~4,100 行
Swift 檔案數量 30
Git commits 90
開發時間 6 天(兼職,1/11 - 1/16)
手動寫的程式碼 < 5%

功能清單:

  • 自動掃描和連接 Sony 相機
  • 背景 GPS 同步(App 在背景時持續運作)
  • Live Activity 即時顯示同步狀態
  • 相機斷線自動重連
  • 電池優化的智慧掃描
  • 支援 iOS 15.0+,完整功能需 iOS 16.1+

我學到的事

  1. Vibe Coding 是玩真的:對有經驗的開發者來說,AI 真的能把生產力拉高個 5 到 10 倍。不過關鍵在於——你得有能力驗證、有能力引導它的輸出,不然一切都是空談。

  2. 你怎麼問,它就怎麼答:指示含糊,拿到的就是含糊的程式碼。把需求、限制、邊界情況講得越具體,Claude 就越能端出專業級的解法。

  3. Opus 4.5 的差別:跟之前的模型比起來,Opus 4.5 在理解複雜系統、維持上下文一致性、主動想到邊界情況這幾點上,進步真的很明顯。

  4. 它不是拿來取代學習的:AI 讓你更快把想法做出來沒錯,可是底層技術你還是得懂。不然 AI 給的建議到底對不對,你連判斷的底氣都沒有。

寫在最後

AlphaGPS 現在已經是我天天在用的工具了。每次出門拍照,相機一開機就自動連上、照片自動標好位置,全程連手機都不用碰一下,真是頗爽。

回頭看這整個專案,我覺得最有意思的其實不是程式碼本身,而是這種全新的開發方式:我只要專心想「我要什麼」跟「為什麼要」,「怎麼做」那些細節就交給 Claude 去傷腦筋。

我想這大概就是 Vibe Coding 的精髓吧——它不是要把你取代掉,而是讓你能把力氣花在真正重要的事情上。

如果你也想試試,我的建議是:先找一個你是真心想解決的問題,然後就去跟 Claude 聊聊看。我猜你大概會被它能帶你走多遠給嚇到喔。


這篇文章本身也是用 Claude 協助撰寫的——用 AI 寫一篇關於 AI 開發的文章,似乎也是剛好而已。