最近「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 隨意生成的建議——它是基於 iOS 開發的最佳實踐,考慮到 CoreBluetooth 的 Delegate 模式和 SwiftUI 的響應式特性。

第二到第三天(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)

跳過任何一步,相機會靜默忽略資料——沒有錯誤回報。

當我把這個發現告訴 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。有這種程度的理解,除錯快了很多。

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 commits:

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. Claude 4.5 Opus 的差異:相比之前的模型,Opus 4.5 在理解複雜系統、保持上下文一致性、主動考慮邊界情況方面有明顯提升。

  4. 這不是取代學習:AI 讓你更快實現想法,但你還是需要理解底層技術。否則你無法判斷 AI 的建議是否正確。


寫在最後

AlphaGPS 現在是我日常使用的工具。每次出門拍照,相機開機就自動連接、照片自動標記位置、全程不需要碰手機。

回頭看這個專案,最有意思的不是程式碼本身,而是這種新的開發方式:我專注於「想要什麼」和「為什麼」,Claude 幫我處理「怎麼做」的細節。

這就是 Vibe Coding 的精髓——不是讓 AI 取代你,而是讓你把精力放在真正重要的事情上。

如果你也想嘗試,我的建議是:找一個你真正想解決的問題,然後跟 Claude 聊聊。你可能會驚訝於它能帶你走多遠。


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