Bundles and Packages

做者:Mattt,原文連接,原文日期:2018-12-17git

譯者:WAMaker;校對:numbbbbbBigNerdCoding;定稿:Forelaxgithub

在這個給予的季節,讓咱們停下腳步,思考一個現代計算機系統賜予咱們的最棒的禮物:抽象。編程

在數百萬 CPU 晶體管、SSD 扇區和 LCD 像素共同協做下,全球數十億人可以平常使用計算機和移動設備而對此全然不知。這一切都應歸功於像文件,目錄,應用和文檔這樣的抽象。swift

這周的 NSHipster,咱們將討論蘋果平臺上兩個重要的抽象:包與包裹。🎁xcode


儘管是不一樣的概念,包與包裹這兩個術語常常會被替換使用。毫無疑問,形成困惑的部分緣由出自它們類似的名稱,但或許主要緣由是許多包剛好也是包裹(反之亦然)。安全

在咱們深刻以前,先定義一下這兩個術語:bash

  • 包是指具備已知結構的,包含可執行代碼,以及代碼所需的資源的目錄。
  • 包裹是指在訪達中看起來像是文件的目錄。

下圖展現了包與包裹之間的關係,將應用、框架包、插件包和文檔分別放入一個或多個分類之中: app

diagram

若是對二者的區別你依然感到困惑,這個類比或許能幫助你理解: 把包裹想象成是一個內容被隱藏的盒子(📦),做爲一個獨立的實體而存在。這點與包不一樣,包更像是一個揹包(🎒) —— 每一款都有特殊的口袋和隔層用來攜帶你須要的東西,不一樣的配置用以決定是帶去學校,去工做,仍是去健身房。若是某樣東西既是包也是包裹,恰似行李(🧳)通常:像盒子同樣渾然一體,像揹包同樣分隔自如。框架

包(Bundles)

包爲代碼和資源的組織提供了特定結構,意在提高開發者的體驗。這個結構不只容許預測性的加載代碼和資源,同時也支持相似於本地化這樣的系統性特性。編程語言

包分屬於如下三個類別,每一種都有它本身特殊的結構和要求:

  • 應用包(App Bundles):包含一個能被啓動的可執行文件,一個描述可執行文件的 Info.plist 文件,應用圖標,啓動圖片,能被可執行文件調用的接口文件,字符串文件,以及數據文件。
  • 框架包(Framework Bundles):包含動態分享庫所須要的代碼和資源。
  • 可加載包(Loadable Bundles):相似於插件,包含擴展應用功能的可執行代碼和資源。

訪問包內容

對於應用,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 文件。

bundleURLbundleIdentifier 這樣的原數據可以經過 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}")
複製代碼

包裹(Packages)

包裹把相關資源封裝和加固成一個獨立單元,意在提高用戶體驗

知足如下任意一個條件,目錄就會被訪達認爲是包裹:

  • 目錄有相似於 .app.playground.plugin 等特殊擴展。
  • 目錄有一個被一個應用註冊做爲文檔類型的擴展。
  • 目錄具備有擴展屬性,將其指定爲包裹。

訪問包裹中的內容

在訪達中,右鍵展現選中項目的可操做目錄。若是選中項目是包裹,「打開」操做下會出現「顯示包內容」選項。

點擊這個選項會從包裹目錄打開一個新的訪達窗口。

固然,也能夠經過代碼訪問包裹中的內容。包裹的類型決定了獲取內容的最佳方式:

  • 若是包裹有包的結構,前文所說的 Bundle 就能輕鬆勝任。
  • 若是包裹是一個文檔,在 macOS 上使用 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

相關文章
相關標籤/搜索