CDNMac 工程技術文章

驗收 iOS 分享延伸功能的輸入類型與暫存檔案

驗收 iOS 分享延伸功能的輸入類型與暫存檔案

使用者從相簿將圖片分享到 iOS App,分享延伸功能出現在選單中,點開後卻提示「無法讀取」。這類問題不能只檢查延伸功能是否成功編譯:來源 App 提供的是 NSItemProvider,它可能宣告多種型別;以檔案形式提供的輸入,也可能只在載入回呼期間有可用的暫存路徑。把「能顯示」「能讀取」「能交給主 App」分開驗收,才能找出問題出在哪個環節。

先列出輸入契約

選定實際需要支援的來源與資料,不要把「所有圖片」寫成一項模糊的測試。至少記錄來源 App、來源內容、registeredTypeIdentifiers、預期讀取方式,以及主 App 最終應取得的結果。同一張圖片從不同來源分享,可能以檔案、資料或兩者的形式提供;延伸功能不能只依賴陣列中的第一個型別。

輸入 延伸功能應檢查 驗收結果
一張圖片 是否有符合圖片型別的表示形式 能讀取內容並建立交接紀錄
多張圖片 每個附件各自宣告的型別 附件數量與成功、失敗數量一致
純文字 是否提供文字表示形式 依產品需求接收或明確拒絕
不支援的內容 沒有相符型別 顯示容易理解的錯誤,不建立空白紀錄

分享延伸功能的啟用規則,決定它何時出現在選單中;執行時的型別檢查,決定它能否處理目前的附件。兩者都要測,不能用「選單裡看得到」取代讀取驗收。

先看宣告,再選表示形式

開發時,將附件的型別識別碼寫入本機診斷紀錄,但不要連同使用者內容上傳。選擇型別時,應檢查它是否符合目標型別,而不是寫死某個來源 App 提供的完整識別碼。若產品只接收圖片,就讓文字輸入走明確的拒絕流程;不要試圖把未知內容強行解碼成圖片。

在回呼中複製檔案

處理檔案表示形式時,最危險的做法是儲存 loadFileRepresentation 回傳的 URL,等延伸功能結束後才讓主 App 開啟。這個 URL 指向載入期間提供的暫存檔案,不能當作長期有效的交接位址。

驗收標準不是「能在回呼中印出路徑」,而是「回呼結束後,主 App 仍能讀到自己有權存取的副本」。

以下 Swift 程式碼示範圖片檔案的關鍵處理邊界。若延伸功能與主 App 需要共用檔案,應設定同一個 App Group,並透過部署設定傳入群組識別碼;不要將範例識別碼直接寫進專案。複製目標使用新檔名,避免覆蓋另一筆分享作業。

import Foundation
import UniformTypeIdentifiers

enum ShareCopyError: Error {
    case unsupportedType
    case containerUnavailable
    case missingFile
}

func copySharedImage(
    from provider: NSItemProvider,
    groupID: String,
    completion: @escaping (Result<URL, Error>) -> Void
) {
    guard let typeID = provider.registeredTypeIdentifiers.first(where: {
        UTType($0)?.conforms(to: .image) == true
    }) else {
        completion(.failure(ShareCopyError.unsupportedType))
        return
    }

    provider.loadFileRepresentation(forTypeIdentifier: typeID) { source, error in
        if let error {
            completion(.failure(error))
            return
        }
        guard let source else {
            completion(.failure(ShareCopyError.missingFile))
            return
        }
        guard let container = FileManager.default
            .containerURL(forSecurityApplicationGroupIdentifier: groupID) else {
            completion(.failure(ShareCopyError.containerUnavailable))
            return
        }

        do {
            let directory = container.appendingPathComponent(
                "Incoming", isDirectory: true
            )
            try FileManager.default.createDirectory(
                at: directory, withIntermediateDirectories: true
            )
            let ext = UTType(typeID)?.preferredFilenameExtension ?? "bin"
            let target = directory.appendingPathComponent(
                UUID().uuidString + "." + ext
            )
            try FileManager.default.copyItem(at: source, to: target)
            completion(.success(target))
        } catch {
            completion(.failure(error))
        }
    }
}

這段程式碼只處理單一圖片附件。實際實作還需彙整多個附件的結果、限制可接受的檔案大小,並決定部分成功時要保留已複製的檔案,還是全部復原。交給主 App 的應是受控目錄中的檔案識別資訊;主 App 讀取後,再依業務規則清理,不能讓副本無限累積。

將讀取邊界納入自動化測試

先為型別選擇函式與複製函式撰寫測試,再測試完整的分享介面。測試至少要涵蓋圖片型別、不支援的型別、載入錯誤、共用容器無法使用、多個附件部分失敗,以及同名輸入不會互相覆蓋。測試替身應能控制回呼時機:在回呼期間提供檔案,回呼返回後移除來源檔案,再確認複製目標仍可讀取。如此驗證的是檔案生命週期,而不只是一個非空的 URL。

在 CDNMac 雲端 Mac 上,先查看專案公開的 scheme 與可用的模擬器,再執行對應測試。以下命令要求執行者將三個環境變數設為目前專案的真實值;結果套件放進新建目錄,避免重新執行時與既有路徑衝突。

: "${PROJECT:?set PROJECT to an existing .xcodeproj path}"
: "${SCHEME:?set SCHEME to a shared test scheme}"
: "${SIMULATOR_ID:?set SIMULATOR_ID to an available device ID}"

xcodebuild -list -project "$PROJECT"
xcrun simctl list devices available

RESULT_ROOT="$(mktemp -d)"
xcodebuild -project "$PROJECT" \
  -scheme "$SCHEME" \
  -destination "platform=iOS Simulator,id=$SIMULATOR_ID" \
  -resultBundlePath "$RESULT_ROOT/share-input.xcresult" \
  test

CI 應保存測試結果套件,並以測試程序的結束狀態判定工作是否成功。不要只搜尋日誌中的「passed」:延伸功能啟動失敗,或目標測試未納入 scheme,也都需要被發現。首次接入時,刻意讓一項斷言失敗,確認流程確實會因此中止。

用實際分享補足測試盲點

單元測試能驗證自己的讀取邏輯,卻無法保證來源 App 會提供哪種表示形式。選一台測試裝置,從預計支援的來源逐項分享:先確認延伸功能出現,再檢查實際宣告的型別、讀取結果,以及主 App 中的副本。大圖片與多張圖片輸入尤其需要觀察記憶體用量、等待時的回饋及取消行為;不要只憑一張小圖通過就結束驗收。

排查問題時依序定位:沒有出現,就檢查啟用規則;出現但型別不符,就記錄來源提供的識別碼;載入失敗,就保留錯誤類別;延伸功能讀取成功但主 App 失敗,就核對 App Group 權限、是否在回呼中完成複製,以及主 App 取得的是否為副本路徑。測試紀錄只保留必要的型別、階段與錯誤資訊,避免將分享內容寫入建置日誌。

交付前的檢查項目

發布前確認四件事:延伸功能的宣告與實際支援的型別一致;每個檔案都在載入回呼中完成複製;主 App 只讀取授權目錄中的副本;拒絕、取消及部分失敗都有可見的結果。若變更了 App Group 設定,還要在全新安裝的測試裝置上重新跑一次交接流程,不能只驗證開發機上既有的容器。

將型別矩陣、自動化測試結果與裝置分享紀錄放在同一次變更中,日後新增輸入格式時,就有明確的起點:先補上契約與失敗測試,再加入讀取實作,而不是等使用者回報「分享後沒有檔案」才回頭猜測來源。

常見問題

延伸功能取得的檔案 URL 能直接交給主 App 嗎?

不要直接交接。應在 loadFileRepresentation 回呼內複製到主 App 可讀取的位置,再傳遞新檔案的識別資訊。

模擬器測試通過後,實機還要確認什麼?

從目標來源 App 實際分享,確認延伸功能是否出現、提供的類型、大型檔案的處理結果,以及主 App 能否讀取複製後的檔案。

CDNMac M4

讓下一次 iOS 建置在專屬雲端 Mac 上執行

採用 M4、16GB RAM 與 256GB SSD 的固定實體環境,可從六個節點與四種租期中,選擇最適合目前任務的組合。

立即租用