雲端 Mac 上的 iOS 彙總進度驗收:權重、取消與完成語意
批次處理檔案時,進度列先跑到百分之百,接著卻跳出失敗訊息;使用者按下取消,背景工作仍然提交結果。這通常不是繪製問題,而是把工作量、取消訊號與業務終態混在了一起。在 GNUMac 雲端 Mac 上,可以先用不依賴網路與模擬器的單元測試確立這些契約,再驗證 iOS 介面。
先約定進度代表什麼
本文以「轉換四份輸入,再驗證兩份輸出」作為測試情境。轉換占總工作量的六成,驗證占四成。這是測試指定的權重,不是效能量測結果,也不代表剩餘時間。
父進度的總量設為一百,子進度則分別以檔案數計量。不要直接把轉換檔案數與上傳位元組數相加;不同單位必須先對應到父層權重。
業務狀態另行區分為等待、執行中、取消中、已取消、失敗與成功。進度物件只負責描述數量,不負責錯誤分類。
百分比回答「約定的工作量完成了多少」,業務狀態回答「結果是否已可靠交付」。兩者不能互相取代。
實作前先寫明三項約束:每個子進度只能掛接一次;完成量不得超過總量;提交結果前必須檢查是否已取消。父子物件由單一任務持有,不要放進所有任務共用的全域變數。
用確定性測試驗收權重
在現有專案的單元測試目標中新增測試檔案,加入以下程式碼。測試只使用 Foundation 與 XCTest,不需要等待真正的檔案轉換,也不靠休眠猜測執行時序。
import Foundation
import XCTest
final class WeightedProgressTests: XCTestCase {
func testWeightedStages() {
let root = Progress(totalUnitCount: 100)
let convert = Progress(totalUnitCount: 4)
let verify = Progress(totalUnitCount: 2)
root.addChild(convert, withPendingUnitCount: 60)
root.addChild(verify, withPendingUnitCount: 40)
XCTAssertEqual(root.fractionCompleted, 0, accuracy: 0.000001)
convert.completedUnitCount = 2
XCTAssertEqual(root.fractionCompleted, 0.3, accuracy: 0.000001)
verify.completedUnitCount = 1
XCTAssertEqual(root.fractionCompleted, 0.5, accuracy: 0.000001)
convert.completedUnitCount = 4
verify.completedUnitCount = 2
XCTAssertEqual(root.fractionCompleted, 1, accuracy: 0.000001)
}
}
這裡刻意讓兩個階段交錯推進,確認父進度不會只反映目前的階段。浮點數結果應以容許誤差比較,不要把百分比格式化成字串後再做斷言。
在雲端 Mac 的測試導覽器執行這個類別,保存提交雜湊值、測試結果與使用的工具鏈資訊。如果已有命令列測試入口,只需加入這個測試目標,不必為進度測試另外建立一套模擬器矩陣。
取消必須由執行器配合
Progress.cancel() 是訊號,不會強制終止正在執行的迴圈。每處理一個工作單元前都要檢查狀態,確實完成後才增加計數;最終替換輸出檔案前,也必須再次檢查。
以下測試驗證「取消後不啟動下一個工作單元」。它不代表已涵蓋並行競爭條件,可以直接加入前面的測試類別。
func testCancellationStopsNextUnit() {
let progress = Progress(totalUnitCount: 4)
progress.isCancellable = true
var processed = 0
func runNextUnit() {
guard !progress.isCancelled else { return }
processed += 1
progress.completedUnitCount += 1
}
runNextUnit()
progress.cancel()
runNextUnit()
XCTAssertTrue(progress.isCancelled)
XCTAssertEqual(processed, 1)
XCTAssertEqual(progress.completedUnitCount, 1)
}
非同步執行器還需要兩個可控制的暫停點:開始耗時操作之前,以及提交結果之前。測試先暫停任務、發出取消訊號,再放行,並斷言沒有啟動新工作,也沒有發布最終結果。應使用測試閘門協調執行,不要固定等待幾秒來碰運氣。
如果底層操作提供取消介面,應同步轉送訊號。若無法中斷,就等它返回後捨棄結果,並清理暫存檔案;介面維持「取消中」,不要提前承諾工作已經停止。
不要掩飾未知總量與重試
尚未掃描出檔案總數時,先顯示「正在統計」,不要拿零總量硬算百分比。掃描結束後固定任務清單,再建立完整的父子關係,開始處理。
執行中持續增加分母,會讓進度倒退。若確實需要動態探索任務,應顯示階段狀態或已處理數量,並清楚說明目前的百分比只適用於工作量已確定的階段。
重試時也不要反覆將同一個子進度歸零。業務層可以保留「第幾次嘗試」,進度層則依成功完成的邏輯單元累計。一次失敗的嘗試,不等於又完成了一份檔案。
也要為零輸入另外訂定規則:若空輸入合法,直接回傳「無需處理」的成功結果;若呼叫端必須提供輸入,就在啟動前回報錯誤,不要建立一個永遠停在起點的任務。
落實到介面與持續驗收
觀察到進度變更後,應切回主執行器更新介面狀態。每次執行都分配獨立的任務識別碼,回寫時核對識別碼,避免上一輪任務的延遲通知覆寫新進度。
顯示百分比時可以取整,但不要用取整後的數值判斷成功。最後一個檔案處理完後,若還需要驗證與原子提交,應事先為這些步驟保留工作量;失敗時保留錯誤狀態,不要強制補滿計數。
合併前逐項檢查:
- 中間權重與交錯推進的斷言皆通過,結果不受執行速度影響。
- 開始前取消、處理中取消、提交前取消都有獨立測試案例。
- 錯誤路徑不會被對應為成功,暫存輸出能夠清理。
- 新任務啟動後,舊任務的通知無法修改目前的介面。
- 空輸入、未知總量與重試都有明確的顯示規則。
最終交付的不是一條看起來平順的進度列,而是一份可驗證的狀態契約。先讓計數可信,再讓取消可控,最後讓成功只代表結果真正可用。
常見問題
Progress 到達百分之百就能顯示成功嗎?
不能。計數完成只描述工作量,業務成功仍須確認最終寫入、驗證或交付成功。介面應另外檢查業務狀態。
呼叫 cancel() 會自動停止正在執行的工作嗎?
不會。執行端必須檢查取消狀態,並將取消傳遞給實際的網路或運算任務;提交最終結果前也要再次檢查。
為你的工作流程配置一台專屬 Mac mini
依照專案需求挑選機型、節點與租用期間。下單前先確認記憶體、儲存空間和連線需求;實際可用狀態以控制台即時回報為準。