[譯] 從現有的程式碼庫建立 Swift 包管理器

iWeslie發表於2019-03-01

Swift 包管理器(SPM)非常適合編寫快速工具,你甚至可以從應用程式中提取現有程式碼。訣竅是你需要意識到你可以將資料夾符號連結到 SPM 專案中,這意味著通過一些工作你可以建立一個包裝生產程式碼部分的命令列工具。

你為什麼要這麼做?

雖然它很依賴於專案,但是常見的用例是建立支援、除錯和持續整合(CI)驗證工具。例如,許多應用程式為了實現功能而使用遠端的資料,應用程式需要將遠端資料轉換為自定義的型別,並且使用業務規則對此資料執行有用的操作。在此流程中會有多個故障點顯示出應用程式的崩潰或不正確的行為,因此解決的方法是在有附加偵錯程式的情況下啟動並進行除錯,Swift 包管理器將是一個幫助發現問題並潛在地阻止問題的好工具。

注意事項

你不能使用 UIKit 框架的下的程式碼,因為這項技術僅適用於基於 Foundation 庫的程式碼。雖然這聽起來有限制,但是在理想情況下,業務邏輯和資料操作的程式碼都不應該都不應該引入有關 UIKit 框架下的東西。

具有依賴性導致了該技術更加難,不過你仍然可以使用它,但是需要在 Package.swift 中進行更多的配置。

你要怎麼做呢?

這取決於你的專案結構。我這裡有一個示例專案。這是一個小型的 iOS 專案,它顯示了一個部落格的帖子列表(你並不需要看專案本身,專案本身並不重要)。專案中部落格的帖子來自於假的 JSON 資料,它沒有特別好的結構,因此應用程式需要進行自定義解碼。為了保持它的輕量級,我將以下面的方式構建最簡單的包裝器:

  • 從標準輸入中讀取
  • 使用生產解析程式碼
  • 列印解碼結果或錯誤

你可以瘋狂地給它新增更多的更能,但是這個簡單的工具將會讓我們在不啟動模擬器的情況下,快速為我們提供關於生產程式碼是否可以接受某些 JSON 的反饋或者顯示任何可能發生的錯誤。

這個示例專案的基礎結構如下:

.
└── SymlinkedSPMExample
    ├── AppDelegate.swift
    ├── Base.lproj
    │   └── LaunchScreen.storyboard
    ├── Info.plist
    ├── ViewController.swift
    └── WebService
        ├── Server.swift
        └── Types
            ├── BlogPost.swift
            └── BlogPostsRequest.swift
複製程式碼

我特意建立了一個僅包含我想重用的程式碼的 Types 目錄。想要建立利用次生產程式碼的命令列工具,我們可以執行以下操作:

mkdir -p tools/web-api
cd tools/web-api
swift package init --type executable
複製程式碼

現在我們已經搭建了一個可以操作的專案。首先讓我們把生產程式碼進行連結:

cd Sources
ln -s ../../../SymlinkedSPMExample/WebService/Types WebService
cd ..
複製程式碼

你要給這個連結使用相對路徑,否則在遷移到別的電腦上時會奔潰

現在專案的結構現在看起來像這樣:

.
├── SymlinkedSPMExample
│   ├── AppDelegate.swift
│   ├── Base.lproj
│   │   └── LaunchScreen.storyboard
│   ├── Info.plist
│   ├── ViewController.swift
│   └── WebService
│       ├── Server.swift
│       └── Types
│           ├── BlogPost.swift
│           └── BlogPostsRequest.swift
└── tools
    └── web-api
        ├── Package.swift
        ├── README.md
        ├── Sources
        │   ├── WebServer -> ../../../SymlinkedSPMExample/WebService/Types/
        │   └── web-api
        │       └── main.swift
        └── Tests
複製程式碼

現在我需要更新 Package.swift 檔案來給程式碼建立一個新的 target 並且新增一個依賴,從而使得 web-api 可執行檔案可以使用這些生產程式碼

Package.swift

// swift-tools-version:4.0

import PackageDescription

let package = Package(
    name: "web-api",
    targets: [
        .target(name: "web-api", dependencies: [ "WebService" ]),
        .target(name: "WebService"),
    ]
)
複製程式碼

既然 SPM 知道如何構建專案,那就讓我們利用生產解析程式碼來寫之前提到的程式碼吧。

main.swift

import Foundation
import WebService

do {
  print(try JSONDecoder().decode(BlogPostsRequest.self, from: FileHandle.standardInput.readDataToEndOfFile()).posts)
} catch {
  print(error)
}
複製程式碼

有了這些後,我們現在可以開始通過這個工具執行 JSON 來看看生產程式碼是否會處理它:

以下是我們嘗試通過這個工具傳送有效 JSON 時的樣子:

$ echo '{ "posts" : [] }' | swift run web-api
[]

$ echo '{ "posts" : [ { "title" : "Some post", "tags" : [] } ] }' | swift run web-api
[WebService.BlogPost(title: "Some post", tags: [])]

$ echo '{ "posts" : [ { "title" : "Some post", "tags" : [ { "value" : "cool" } ] } ] }' | swift run web-api
[WebService.BlogPost(title: "Some post", tags: ["cool"])]
複製程式碼

下面是當我們輸入無效的 JSON 時所得到的錯誤資訊示例:

$ echo '{}' | swift run web-api
keyNotFound(CodingKeys(stringValue: "posts", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"posts\", intValue: nil) (\"posts\").", underlyingError: nil))

$ echo '{ "posts" : [ { } ] }' | swift run web-api
keyNotFound(CodingKeys(stringValue: "title", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "posts", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"title\", intValue: nil) (\"title\").", underlyingError: nil))

$ echo '{ "posts" : [ { "title" : "Some post" } ] }' | swift run web-api
keyNotFound(CodingKeys(stringValue: "tags", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "posts", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"tags\", intValue: nil) (\"tags\").", underlyingError: nil))
複製程式碼
  • 第一個例子是錯誤的,因為它沒有 posts
  • 第二個例子也是錯誤的,因為 posts 沒有 title
  • 第三個例子還是錯誤的,因為 posts 沒有 tags

在實際應用中,我將用管道的方式輸出一個實時或暫存斷點的 curl 結果,而非手寫的 JSON 程式碼。

這真的很酷,因為我可以看到生產程式碼沒有解析其中的一些示例,並且我可以看到解釋了錯誤原因的資訊。如果沒有這個工具,我需要手動執行應用程式並找出一種方法來獲取不同的 JSON 有效負載而來執行解析邏輯。

總結

本文介紹了通過 SPM 使用生產程式碼來建立工具的基本技術。你可以真正地執行它並建立一些漂亮的工作流程,例如:

  • 將該工具新增為 web-api 的持續整合管道中作為一個步驟,以確保使移動客戶端崩潰的部署不會發生。
  • 展開該工具以應用業務規則(來自生產程式碼)以檢視是否在提要,解析或業務規則層級引入了錯誤。

我已經開始在我自己的專案中使用這個想法了,我很高興它能幫助我和我團隊的其他成員。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

[譯] 從現有的程式碼庫建立 Swift 包管理器

相關文章