解構 iOS 15 的 StoreKit 2 了解十多年來第一次的大更新!


本篇原文(標題:Play Around With StoreKit 2 in iOS 15)刊登於作者 Medium,由 Mark Lucking 所著,並授權翻譯及轉載。

StoreKit 早在 iOS 3.0 就已經推出了,Apple 雖然一直有作更新,但都只是一些小調整。直到今年在 WWDC 2021,Apple 推出了 StoreKit 2,可以說是十多年來第一次重大的更新。

在這篇文章中,我會帶大家簡單看看這個新框架的客戶端程式碼 (client-side code)。在此之前,請留意我們需要 iOS 15 才能支援 StoreKit 2.0,如果你使用的是 iOS 14 或以下的版本,就應該參閱這篇文章

從 WWDC 的介紹和其程式碼,我可以看出 Apple 決定把它命名為 StoreKit 2 的原因。新的 API 分為五個部分:products、purchases、transaction info、transaction status、和 subscription status。

這個版本有一些重大的更新,包括用了新的並行異步等待語法 (concurrency async await syntax) 來取代委派模式 (delegate pattern),另外 receipt 格式也更改為 JSON,利用 JSON Web Signature(即 x5c 標准)來簽名,每筆交易都會有一張 receipt。UIKit 的支持者要注意,我下載了範例 App,可以看到它是建基於 SwiftUI 界面的。

介紹中提到的另一個大更新,與使用者體驗 (user experience) 相關,就是在重新安裝 App 時,StoreKit 2 會自動回復先前的購買內容。

這個範例程式碼非常全面,我們可以藉此完全了解 StoreKit 的更新。讓我們一起來看看程式碼,並做一些逆向工程 (reverse engineer),來了解新 StoreKit 框架的構造吧!

範例 App

首先,讓我們從 Apple 下載完整的程式碼。在 Xcode 打開範例,點擊 Product->Scheme->Edit Scheme 來更改 StoreKit 設定。

xcode-edit-scheme

我們只需要做一個小更改,就是把 StoreKit Configuration 轉為 local configuration:

configuration
A screenshot is taken from Xcode version 13.1

執行 App 之後,我們會看到左邊的畫面,當中有兩個選擇,第一個 My Cars 可以看看我們有甚麼車,而第二個 Shop 就是讓我們買更多車。因為我們現在還沒有車,我就先不放 My Cars 的截圖了。

Screenshots of Apple’s StoreKit 2 Demo App

執行 App 後,你可以隨意試試它的功能,試著買一輛車、或是訂閱導航服務 (navigation subscription)。買了之後,在 My Cars 的子選單中,就會出現其他購買燃料 (fuel) 和電力 (power) 的選項。

拆解程式碼

StoreKit 有非消耗品 (non-consumable)、消耗品 (consumable) 和訂閱 (subscription)。在範例 App 中,就設置了一個縱向 (portrait) 的導航視圖 (Navigation View)。

除了最上面 ContentView導航視圖之外,這個 App 還有 9 個視圖,不過我認為大家最有興趣的,是 Store.swift 這個 Swift 檔案,上述視圖就是引用這個檔案為環境物件 (environmental object) 的。環境物件就像是 SwiftUI 版本的 singleton,而又沒有 singleton 的缺點。

看看 Store.swift 的程式碼,我們會發現當中用了 @MainActor 標籤,這在 SwiftUI 中是 DispatchQueue.main.async. 的替代品。

這裡大概有 6 個方法,主要類別 @Publishing 包含了這個範例 App 的主要變數,也就是汽車、燃料、訂閱和 purchasedIdentifiers。Apple 選擇使用 plist 來儲存產品的本地資訊 (local details),在 Store 類別的程式碼初始化時讀取。

plist 調用了兩個方法,前者設置了一個 listener 來捕捉交易,後者是一個本地 StoreKit 配置的請求,以回傳 plist 中的產品資訊。這兩個方法都使用了新的並行異步等待語法。

updateListenerTask = listenForTransactions()Task {<br>  //Initialize the store by starting a product request.<br>  await requestProducts()<br>}

在前者的方法中,listener 會進入一個循環 (loop),這個循環除非特別去取消它,否則不會退出。而它只有在類別本身被銷毀 (destroy) 時才會調用。

我找到的另一個方法是 purchase,這個方法是通過點擊 buy 按鈕調用的,App 內有幾個不同的選單都引用了這個方法。同樣,這只是一個 task,而它的回應會被前文所述的 listener 接收。

Task {<br>  await purchase()<br>}

除此之外,我們還有一個 isPurchased 方法,它會回傳最近一次 purchase 的 JSON 格式 packet,並傳遞給 isPurchased 的第二個方法,以驗證 (verify) 購買的產品,這是 Apple 為我們做的驗證。

guard let result = await Transaction.latest(for: productIdentifier) else {<br>  //If there is no latest transaction, the product has not been purchased.<br>  return false<br>}

有趣的是,驗證方法是使用泛型 (generic) 來回傳結果。

func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {<br>  switch result {<br>    case .unverified:<br>      throw StoreError.failedVerification    case .verified(let safe):<br>      return safe<br>    }<br>}

AppAccountToken

StoreKit2 還有一個新功能,就是一個開發者創建的 UUID appAccountToken,雖然這沒有出現在範例程式碼中,但 Apple 有在 WWDC 介紹中提到。

appAccountToken 可以連繫到 App 內的帳戶,如此一來,交易資料就不限於設備 ID 或 Apple ID,而是我們可以追踪到 App 使用者帳戶的交易。這樣可以將非消耗品、消耗品和訂閱的交易,連接到 App 的某個使用者,而不是 AppleID 或 設備。

手動驗證 (Manual Verification)

WWDC2021 StoreKit 2 介紹中最後的部分,Apple 講述了加密 (encryption) 的主題。他們可以是想把最困難的部分留到最後,讓更集中、可以聽完整個介紹的人來了解這個部分。在最後這部分中,Apple 概述了 JWS packet 的組成。

storekit-2
截圖來源:WWDC 影片

有趣的是,這裡提到的演算法是 CryptoKit 原生的。不過演講的人沒有講解掩蓋驗證簽名的方法,只是建議我們去 Google 一下。

他也提供了一些建議:

  • 我們應該確認交易中的 appID 與 App 匹配,因此我們需要把 appID 嵌入到 App 中。
  • 我們應該執行設備驗證檢查,也就是執行以下的程式碼,以確認收據中的簽名信息是為此設備產生的。
storekit-2
截圖來源:WWDC 影片

這篇文章到此為此,謝謝你的閱讀。希望這篇文章可以幫助到你。

特別鳴謝 Anupam Chugh。

本篇原文(標題:Play Around With StoreKit 2 in iOS 15)刊登於作者 Medium,由 Mark Lucking 所著,並授權翻譯及轉載。

作者簡介:Mark Lucking,編程資歷超過 35 年,熱愛使用及學習 Swift/iOS 開發,定期在 Better ProgrammingThe StartUpMac O’ClockLevel Up Coding、及其它平台上發表文章。

譯者簡介:Kelly Chan-AppCoda 編輯小姐。


此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。

blog comments powered by Disqus
Shares
Share This