iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
Vibe Coding

神隊友.swift:30 天 Vibe Coding 打造育兒 iOS App 系列 第 10 篇

[Day 10] 架構文件:免費 Apple ID 能做到哪裡

  • 分享至 

  • xImage
  •  

Day 10

文章同步發表在我的個人 Blog

架構文件:免費 Apple ID 能做到哪裡

昨天定完第一版要做什麼,今天照理說可以開始寫程式了,我還是先花時間做了一份架構文件。根據 Day 7 的內容分工,把需求規格交給 Codex 整理,我自己來審。

先講結果:我沒有申請 Apple 的開發者帳號,沒有 CloudKit 也沒有推播,所以同步得自己做。整個架構幾乎都是被這件事決定的。

1. 免費帳號的邊界

Apple 有一張官方對照表,最右邊那欄就是免費帳號:

Capability 免費帳號 影響
App Groups ✅ Share Extension、Widget 和主 App 共用同一個資料庫
Keychain 共用 ✅ 憑證可以跨 extension 共用
iCloud:CloudKit ❌ 不能用內建的雲端同步,要自己做
推播通知 ❌ 沒有 APNs

整理之後發現推播影響最大。推播由他人觸發、你的手機自己跳出通知,這需要經過 Apple 的伺服器;本機通知是自己觸發自己,例如晚上八點提醒,這個可以用,但它只知道自己手機上的資料。所以太太在她手機產生一筆紀錄,我這邊不會跳通知,需要打開 App 才看得到。

CloudKit 細看之後也有狀況。它有一種 shared database,靠 CKShare 把資料分享給指定的人,本來就是設計給這種情境的。不過 SwiftData 的 ModelConfiguration.CloudKitDatabase 只有三個選項:

static var automatic: ModelConfiguration.CloudKitDatabase
static func `private`(String) -> ModelConfiguration.CloudKitDatabase
static var none: ModelConfiguration.CloudKitDatabase

沒有 shared。 SwiftData 內建的 CloudKit 同步只能同步自己的多台裝置,不能分享給太太的 Apple ID;跨帳號分享要走 Core Data 的 NSPersistentCloudKitContainer。所以就算買了付費帳號,那也不是改一行設定的事。

結論是同步自己做,並且把同步層抽成一個介面,未來如果申請了 Apple 開發者帳號,只需要更換那一層。

2. 系統長什麼樣

系統全貌

兩支手機都裝 App。一支手機上有三個各自獨立的 process:主 App、Share Extension、Widget,靠 App Group 共用同一個資料庫檔案。Widget 在桌面顯示最近幾筆和指派給自己的事,因為沒有推播,它是這一版最接近通知的東西。

雲端只有一個 D1,負責轉送兩支手機之間的資料。

三條核心的決定:

  1. 完整資料留在手機。解析、整理、產生交接卡都在裝置上完成,雲端不做任何運算
  2. 兩支手機同步全部紀錄。交接要看得到對方記了什麼,所以不做挑選
  3. 寫入只有一條路。自己打字、從別的 App 分享進來,都要經過同一個寫入 Service,身分、權限、去重都在那裡做

第一條牽涉到要不要加密。目前的判斷是不用,餵食和睡眠這類作息紀錄沒有敏感到那個程度。順帶查了市面上的育兒 App,做法也是帳號密碼加 HTTPS,沒有加密,業界劃的線是病況和診斷,不是作息。之後如果開放給其他家庭,或者開始記用藥和就醫,這個部分要重新檢討。

第二條的分組方式是一組家庭 ID,伺服器用它決定資料要給誰。配對就是我產生一組邀請碼傳給太太,她輸入之後兩支手機就在同一組裡。

第三條在程式上是主 App 裡的一個 @ModelActor,所有寫入在上面排隊。Share Extension 是獨立的 process,只把內容投進收件匣,由主 App 統一驗證後寫入。

3. 同步:什麼時候送,衝突了怎麼辦

同步的時機是打開 App:先把本機新增的送出去,再拉回對方的更新。離線的時候照常記錄,新增和修改排進待送佇列,下次連上網一起送。

這裡有一個實際會遇到的狀況:我離線改了一筆,對方在線上也改了同一筆,我上線之後要聽誰的。

先看哪些欄位真的會衝突:

欄位 誰能改 會不會衝突
內容、時間 只有記錄的人 不會
請求的答應或婉拒 只有被指派的人 不會
待辦的完成狀態 兩個人都能打勾 會

所以只有一種情況要處理:兩個人同時動同一筆待辦的狀態。

做法是每筆事件帶一個版本號,每次修改加一。送出的時候一起帶上「我改的是第幾版」,伺服器比對版本號,對得上就接受;對不上代表這筆在我離線的時候被改過,伺服器拒絕並回傳最新的那一筆,App 把兩邊的狀態顯示出來讓人自己選。

不用時間戳判定,因為兩支手機的時鐘不保證一致,使用者還能自己調整系統時間。

實務上大部分衝突的結果是一樣的,兩個人都打勾就是完成。真正需要人決定的只有一邊打勾、一邊取消,這種情況很少,所以不做自動合併,跳出來問比較安全。

取消一筆事件是把狀態改成已取消,不做刪除。真的刪掉的話,對方那邊沒收到刪除,舊資料會一直留著。

4. 資料會不會掉

免費帳號的描述檔有期限,Apple 寫得很明白:

Provisioning profiles that enable apps to be installed on a device will expire 7 days from issuance. You'll need to rebuild and reinstall your app to your device after expiration.

從簽發起算七天,到期要重新建置安裝。注意官方寫的是 rebuild and reinstall,沒有說要先刪掉 App。用同一個 Team 和 Bundle ID 覆蓋安裝,資料會不會保留,我的理解是會,但官方沒有明文,要實機驗過才能寫進驗收條件。真正會歸零的是先在手機上刪掉再裝,還有換一支手機。

這兩種情況靠 D1 還原。兩支手機同步的是全部紀錄,D1 上本來就有完整的一份,新裝置安裝完成之後輸入家庭 ID,把紀錄拉回來就還原了,不需要另外做備份檔。

太太那支手機不用每七天拿回來接線。在 Xcode 的 Devices and Simulators 裡配對一次、勾選 Connect via network,之後同一個 Wi-Fi 就能無線重新部署。

5. 還沒解決的問題

語音輸入不保證留在手機上。 Day 9 我寫成系統鍵盤的聽寫在裝置上跑,這裡更正。Apple 的隱私說明寫的是:

When you use Dictation, your device will indicate in Keyboard Settings if your audio and transcripts are processed on your device and not sent to Apple servers. Otherwise, the things you dictate are sent to and processed on the server.

而 iOS 功能供應情況裡,裝置端聽寫的語言清單中文只有粵語(香港)、粵語(中國大陸)、國語(中國大陸),沒有台灣的國語。在實際確認鍵盤設定上的標示之前,介面要提醒用藥、劑量這類內容改用快速按鈕或打字。

家庭 ID 遺失沒有救援機制。 沒有帳號系統,兩支手機靠配對時交換的 ID 認彼此。只要有一支手機還在,重新配對就能把另一支接回來;兩支都掉才真的找不回來。

6. 里程碑

里程碑 內容
M1(Day 11–13) 一句話輸入、規則解析、確認卡,存進 SwiftData
M2(Day 14–15) 交接卡;從別的 App 分享進來
M3(Day 16–17) 首頁近況卡、快速按鈕、指派、購買清單
M4(Day 18–19) 兩支手機同步、新裝置還原、晚上八點的本機提醒
M5(Day 20–21) Widget、入門三題與階段預設
M6(Day 22–23) 裝到家人手機,開始每天實際使用
M7(Day 24–28) 打磨、測試、離線處理

交接卡排在 M2,因為它只要本機紀錄加上系統的分享功能就能做,不用等同步。對方不裝 App 也讀得懂,所以它是最早能拿給太太用的東西。

明天

Day 11 開始實作第一個里程碑:一句話輸入、規則解析、確認卡、存進 SwiftData。順便會講 SwiftData 的 @Model 對照 Eloquent 長什麼樣,還有 schema 變更跟 Laravel migration 差在哪。

參考資源

官方文件


上一篇
[Day 9] 第一版要做什麼:回到「你怎麼沒跟我說」這句話
系列文
神隊友.swift:30 天 Vibe Coding 打造育兒 iOS App 共 10 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言