做者:Mattt,原文連接,原文日期:2018-12-17git
譯者:WAMaker;校對:numbbbbb,BigNerdCoding;定稿:Forelaxgithub
在這個給予的季節,讓咱們停下腳步,思考一個現代計算機系統賜予咱們的最棒的禮物:抽象。編程
在數百萬 CPU 晶體管、SSD 扇區和 LCD 像素共同協做下,全球數十億人可以平常使用計算機和移動設備而對此全然不知。這一切都應歸功於像文件,目錄,應用和文檔這樣的抽象。swift
這周的 NSHipster,咱們將討論蘋果平臺上兩個重要的抽象:包與包裹。🎁xcode
儘管是不一樣的概念,包與包裹這兩個術語常常會被替換使用。毫無疑問,形成困惑的部分緣由出自它們類似的名稱,但或許主要緣由是許多包剛好也是包裹(反之亦然)。安全
在咱們深刻以前,先定義一下這兩個術語:bash
下圖展現了包與包裹之間的關係,將應用、框架包、插件包和文檔分別放入一個或多個分類之中: app
若是對二者的區別你依然感到困惑,這個類比或許能幫助你理解: 把包裹想象成是一個內容被隱藏的盒子(📦),做爲一個獨立的實體而存在。這點與包不一樣,包更像是一個揹包(🎒) —— 每一款都有特殊的口袋和隔層用來攜帶你須要的東西,不一樣的配置用以決定是帶去學校,去工做,仍是去健身房。若是某樣東西既是包也是包裹,恰似行李(🧳)通常:像盒子同樣渾然一體,像揹包同樣分隔自如。框架
包爲代碼和資源的組織提供了特定結構,意在提高開發者的體驗。這個結構不只容許預測性的加載代碼和資源,同時也支持相似於本地化這樣的系統性特性。編程語言
包分屬於如下三個類別,每一種都有它本身特殊的結構和要求:
Info.plist
文件,應用圖標,啓動圖片,能被可執行文件調用的接口文件,字符串文件,以及數據文件。對於應用,playgrounds,以及其它你感興趣的包來講,都能經過 Bundle.main
進行訪問。大多數狀況,可使用 url(forResource:withExtension:)
(或它的一種變體)來獲取特定資源的路徑。
舉例來講,若是應用中包含了一個名叫 Photo.jpg
的文件,用下面的方法能得到訪問它的 URL:
Bundle.main.url(forResource: "Photo", withExtension: "jpg")
複製代碼
若是使用 Asset Catalog,你能夠從媒體庫(⇧⌘M)拖拽到編輯器來建立圖像。
除此以外,Bundle
提供了一些實例方法和變量來獲取標準包內容的位置,返回 URL 或 String 類型的路徑:
URL | Path | 描述 |
---|---|---|
executableURL | executablePath | 可執行文件 |
url(forAuxiliaryExecutable:) | path(forAuxiliaryExecutable:) | 輔助的可執行文件 |
resourceURL | resourcePath | 包含資源的子目錄 |
sharedFrameworksURL | sharedFrameworksPath | 包含共享框架的子目錄 |
privateFrameworksURL | privateFrameworksPath | 包含私有框架的子目錄 |
builtInPlugInsURL | builtInPlugInsPath | 包含插件的子目錄 |
sharedSupportURL | sharedSupportPath | 包含共享支援文件的子目錄 |
appStoreReceiptURL | App Store 的收據 |
全部的應用包都必須有一個包含應用信息的 Info.plist
文件。
bundleURL
和 bundleIdentifier
這樣的原數據可以經過 bundle 實例被直接訪問。
import Foundation
let bundle = Bundle.main
bundle.bundleURL // "/path/to/Example.app"
bundle.bundleIdentifier // "com.nshipster.example"
複製代碼
經過下標能從 infoDictionary
變量得到其餘信息(若是信息要展現給用戶,請使用 localizedInfoDictionary
)。
bundle.infoDictionary["CFBundleName"] // "Example"
bundle.localizedInfoDictionary["CFBundleName"] // "Esempio" (`it_IT` locale)
複製代碼
包的存在讓本地化變得容易。強制本地化資源的存放位置後,系統便能將加載哪一個版本的文件的邏輯從開發者層面抽象出來。
舉個例子,包負責加載應用的本地化字符串。使用 localizedString(forKey:value:table:)
方法就能夠獲取到這些值。
import Foundation
let bundle = Bundle.main
bundle.localizedString(forKey: "Hello, %@",
value: "Hello, ${username}",
table: nil)
複製代碼
然而,一般來講用 NSLocalizedString
會更好,像 genstrings
這樣的工具可以自動取出鍵和註釋到 .strings
文件中便於翻譯。
// Terminal
$ find . \( -name "*.swift" ! \ # 找出全部 swift 文件
! -path "./Carthage/*" \ # 無視 Carthage 與 CocoaPods 的依賴
! -path "./Pods/*"
\) | \
tr '\n' '\0' | \ # 替換分隔符
xargs -0 genstrings -o . \ # 處理帶空格的路徑
複製代碼
NSLocalizedString("Hello, %@", comment: "Hello, ${username}")
複製代碼
包裹把相關資源封裝和加固成一個獨立單元,意在提高用戶體驗。
知足如下任意一個條件,目錄就會被訪達認爲是包裹:
.app
,.playground
或 .plugin
等特殊擴展。在訪達中,右鍵展現選中項目的可操做目錄。若是選中項目是包裹,「打開」操做下會出現「顯示包內容」選項。
點擊這個選項會從包裹目錄打開一個新的訪達窗口。
固然,也能夠經過代碼訪問包裹中的內容。包裹的類型決定了獲取內容的最佳方式:
Bundle
就能輕鬆勝任。NSDocument
或在 iOS 上使用 UIDocument
來訪問。FileWrapper
導航目錄,文件和符號連接,用 FileHandler
來讀寫文件描述。雖然說是由訪達決定如何展現文件和目錄,大多數的判斷會被代理給操做系統以及管理統一類型標識(UTI)的服務。
若是想要肯定一個文件擴展是一個內置系統包裹類型,仍是一個被已安裝的應用使用的文檔類型,調用 Core Services 方法 UTTypeCreatePreferredIdentifierForTag(_:_:_:)
與 UTTypeConformsTo(_:_:)
能知足你的需求:
import Foundation
import CoreServices
func directoryIsPackage(_ url: URL) -> Bool {
let filenameExtension: CFString = url.pathExtension as NSString
guard let uti = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension,
filenameExtension, nil
)?.takeRetainedValue()
else {
return false
}
return UTTypeConformsTo(uti, kUTTypePackage)
}
let xcode = URL(fileURLWithPath: "/Applications/Xcode.app")
directoryIsPackage(xcode) // true
複製代碼
咱們找不到任何描述如何設置所謂的包裹比特(package bit)的文檔,但根據 CarbonCore/Finder.h,在
com.apple.FindlerInfo
擴展參數中設置kHasBundle(0x2000)
標示可以實現:$ xattr -wx com.apple.FinderInfo /path/to/package \ 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00 \ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 複製代碼
正如咱們看到的那樣,並不是只有終端用戶從抽象中獲益 —— 不管是像 Swift 這樣的高級編程語言的安全性和表現力,仍是像 Foundation 這樣的 API 的便利性,做爲開發者也能夠利用抽象開發出優秀的軟件。
或許咱們會抱怨 抽象泄漏 與 抽象反轉 帶來的問題,但重要的是退一步,瞭解咱們天天處理多少有用的抽象,以及它們帶給了咱們多少可能性。
本文由 SwiftGG 翻譯組翻譯,已經得到做者翻譯受權,最新文章請訪問 swift.gg。