玩轉 Xcode Playground(上)

語言: CN / TW / HK

在 Swift 語言推出的同一年(2014 年),蘋果就在 Xcode 中集成了 Playground 功能。相較標準的 Xcode 項目,Playground 啟動更快、使用更輕巧,被廣泛應用於 Swift 語言學習、框架 API 測試、快捷數據處理、彙集靈感等眾多方面。本系列將介紹大量有關 Xcode Playground 的使用技巧,涉及穩定性、第三方庫、資源管理、異步處理、文檔標註等等方面,讓你玩轉 Playground,使其成為你工作學習中的利器。

健康筆記 是我開發的一個iOS app,主要服務於有長期健康管理需求的人士。健康筆記提供了強大的自定義數據類型功能,可以滿足記錄生活中絕大多數的健康項目數據的需要。你可以為每個家庭成員創建各自的健康數據記錄筆記,或者針對某個特定項目、特定時期創建對應的筆記。

推廣

創建與設置

.playground vs .playgroundbook

在 Xcode 中創建的 Playground 項目被保存為以 .playground 為後綴的包(可以通過顯示包內容查看其中的文件)。 .playground 可以直接在 Xcode 和 Swift Playgrounds 3.x 以上的版本打開。

.playgroundbook 是 Swift Playgrounds 特有的包格式,相較 .playground ,它包含了很多獨有的功能,這些功能主要用於改善 Swift Playgrounds 在教育和娛樂方面的體驗。 .playgroundbook 格式只能在 Swift Playgrounds 上打開。

更多關於最新 Swift Playgrounds 4 的介紹請閲讀 Swift Playgrounds 4 娛樂還是生產力

本系列介紹的技巧主要針對 Xcode Playground (也就是 .playground ),多數技巧同時適用於 Xcode 和 Swift Playgrounds,僅適用於單獨平台的技巧會做出明確地標註。

如何創建 Playground 項目

在 Xcode 中創建 Playground 項目

在 Xcode 中,點擊 File -> New -> Playground 即可創建一個格式為 .playground 的 Xcode Playground 項目。

Playground 提供了數種預置模版,模版選擇界面的系統設定( iOS、macOS)僅影響模版代碼,並不會自動設置 Page 的運行環境。

Xcode 可以打開 Playground 項目,也可以將 Playground 項目添加到 Project 或 Workspace 中(有助於測試 SPM 或調用 Project 中自定義的類型)。

在 Swift Playgrounds 中創建 Playground 項目

在 Swift Playgrounds 4 中可以直接創建與 Xcode 兼容的 Playground 項目( .playground )。點擊首頁下方的【查看全部】,選擇其中的 Xcode Playground。

請注意:首頁下方的 Playground 按鈕創建的是 playgroundbook

Swift Playgrounds 創建的項目,默認保存在 iCloud 雲盤的 Playgrounds 目錄中。儘量不要同時在 macOS 的 Xcode 和 iPad 的 Swift Playgrounds 上同時編輯一個項目,容易造成版本衝突。

如何創建多個 Playground Page

Playground 鼓勵開發者每次只關注一個議題,通過將議題分散到不同的 Page 來幫助開發者組織代碼和對應的資源。

新建的 Playground 項目默認只有一個 Page(單 Page 模式下,左側的導航欄中 Page 和 Playground 項目將合併顯示)。每個 Page 都可以設置對應的實時視圖。

在 Xcode 中,通過 File 菜單或在導航欄 Playground 項目上點擊右鍵,可以創建新的 Playground Page。

當不只有一個 Page 後,Playground 項目和 Page 將分開顯示。

在 Swift Playgrounds 4 中,點擊側邊欄的 編輯 按鈕,進入編輯模式,點擊 + 按鈕可創建新的 Page。

可以調整 Page 順序,並可修改每個 Page 的名稱(不僅有助於標識,更方便在不同的 Page 中實現快速跳轉)。

在單 Page 模式下,項目中只有一組 Sources 和 Resources 目錄。在多 Page 模式下,除了項目根目錄下的 Sources 和 Resources 外,每個 Page 也都有自己的 Sources 和 Resources 目錄。

每個 Page 都應視為獨立的 Module,Page A 中的代碼不可被 Page B 調用。

如何調試代碼

Playground 並不提供設置斷點的功能,但是可以通過指定執行結束點或單步執行的方式來滿足部分的調試需求。

在 Xcode 中,通過點擊代碼左側行數上的執行按鈕(需要按鈕的顏色為藍色)來指定當前執行的結束位置。

點擊當前結束位置之後的藍色執行按鈕可以繼續向下執行。點擊代碼編輯區域下方的執行按鈕,將重新執行全部代碼。

在輸入新的代碼後,可以採用輸入 Shift-Return 的方式讓 Playground 執行截至本行尚未執行的代碼。此種方式在不希望反覆執行長耗時代碼段的情況下將非常有用(例如機器學習)。

Swift Playgrounds 沒有提供設置執行結束位置的功能,但提供了單步執行的設定。點擊屏幕下方的儀表按鈕,可以設定調試方式。

提高 Xcode 下的運行穩定性(Xcode Only)

設置運行環境

在 Xcode 中,可以在右側的 Playground Settings 中設定 Playground 的運行環境。

在沒有必須依賴 iOS 框架代碼的情況下,將運行環境設置為 macOS 可以減少因 iOS 模擬器錯誤引發的不穩定狀況。

當有多個 Playground Page 時,可以為每個 Page 單獨設置對應的運行環境。

當有多個 Playground Page 時,點擊最上方的項目名稱,可以為所有 Page 一併設置成統一的運行環境

Swift Playgrounds 僅兼容運行環境為 iOS 的 Playground 項目。

將運行方式改成手動

在運行方式被設置為自動模式時,每當你修改代碼後,系統都會自動運行代碼並顯示結果。自動模式在代碼內容較少且簡單的情況下表現還不錯,不過一旦代碼較多且複雜後,自動運行模式將導致系統資源佔用較多,且容易出現運行不穩定的情況。

長按代碼編輯區域下方的執行按鈕,可以在兩種模式中進行選擇。

在 Xcode 的配置中,可以為 Playground 指定適合的快捷鍵,提高操作效率。

如何查看結果欄

Playground 在 Xcode 中擁有一個獨有的顯示區域——結果欄,該區域不僅可以顯示每行代碼的當前值、歷史狀態,同時也會顯示關於調用次數、文件信息等等內容。

例如上圖中,55 行顯示了圖片的尺寸信息,57 行顯示了 y 在當前行的值,59 行則顯示了在循環中本行的執行次數。

當鼠標靠近右側的屏幕圖標時,將出現眼睛圖標。點擊眼睛圖標將顯示該行代碼對應的 QuickLook 內容。點擊屏幕圖標可以將 QuickLook 內容顯示在代碼編輯區域中。

QuickLook 的內容可以在最新值、歷史記錄、圖表模式間切換(可切換模式的數量將根據類型的不同而有所變化)。

Swift Playgrounds 中對於 QuickLook 的操作與 Xcode 類似,並可通過關閉【啟用結果】來提高代碼的執行效率。

如何創建自定義 QuickLook

蘋果已經為不少的系統類型提供了 Playground 下的 QuickLook 支持。通過讓其他的系統類型(主要集中於較新的 API)以及我們自定義的類型滿足 CustomPlaygroundDisplayConvertible 協議,以提供 QuickLook 支持。

比如説,WWDC 2021 上新推出的AttributedString 目前尚不支持 QuickLook ,但通過在 playgroundDescription 中將其轉換為 NSAttributedString,就可以直接在 Playground 中顯示正確的 QuickLook 了。

下圖為,沒有滿足 CustomPlaygroundDisplayConvertible 協議的狀況。AttributedString 的 QuickLook 為結構體的 Dump 樣式。

蘋果為 NSAttributedString 提供了正確的 QuickLook 實現,將 AttributedString 轉換成 NSAttributedString 以實現更好的顯示效果。

extension AttributedString: CustomPlaygroundDisplayConvertible {
    public var playgroundDescription: Any {
        NSAttributedString(self)
    }
}

健康筆記 是我開發的一個iOS app,主要服務於有長期健康管理需求的人士。健康筆記提供了強大的自定義數據類型功能,可以滿足記錄生活中絕大多數的健康項目數據的需要。你可以為每個家庭成員創建各自的健康數據記錄筆記,或者針對某個特定項目、特定時期創建對應的筆記。

推廣

PlaygroundSupport

什麼是 PlaygroundSupport

PlaygroundSupport 是專門用於擴展 Playground 的框架。提供了在 Playground 中共享數據、管理實時視圖、控制 Playground 運行模式等功能。

在需要的 Playground Page 的代碼中通過 import PlaygroundSupport 導入框架。

如何獲得異步執行的結果(Swift Playgrounds Only)

在老版本的 Xcode(Xcode 12、Xcode 13 已經解決了這個問題)以及 Swift Playgrounds 中,如果不經過特別的設定,Playground 並不會等待異步代碼的返回結果, 在完成了全部的代碼調用後即結束執行。需要將 Playground 設置為無限執行模式後,才會在獲得異步執行的結果後方終止運行狀態。

在 Swift Playgrounds 中執行下面的代碼並不會獲得打印結果

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    print("Hello world")
}

需要導入 PlaygroundSupport ,並設置 needsIndefiniteExecution 為 true 。

import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    print("Hello world")
}

每個 Page 都需要單獨設置,且不能在代碼的最後設定。

如何執行 async/await 代碼

本節內容並不需要 PlaygroundSupport 的支持,但為了同【如何獲得異步執行的結果】章節靠近,故放置在此處

在 Playground 中使用新的 async/await 代碼時,需要導入 _Concurrency 方可正常運行代碼。

import _Concurrency

Task {
    try await Task.sleep(nanoseconds: 2_000_000_000)
    print("Hello world")
}

在 Swift Playgrounds 中執行上述代碼時,需要設置 needsIndefiniteExecution。

如何創建實時視圖

你可以使用實時視圖來為 Playground 添加互動性,試驗不同的用户界面元素,並建立自定義元素。通過導入 PlaygroundSupport 並將當前頁面的實時視圖設置為你的自定義視圖或視圖控制器,就可以將一個交互式實時視圖添加到 Playground Page 中。

實時視圖支持 SwiftUI 視圖以及 UIKit(AppKit)的視圖和視圖控制器。SwiftUI 視圖需要通過 setLiveView 來設定。

import PlaygroundSupport
import UIKit

let lable = UILabel(frame: .init(x: 0, y: 0, width: 200, height: 100))
lable.text = "Hello world"
lable.textAlignment = .center
lable.textColor = .red

//PlaygroundPage.current.setLiveView(lable) UIKit 視圖,兩種設置方法都可以
PlaygroundPage.current.liveView = lable

在設置了實時視圖後,Playground 會自動將 needsIndefiniteExecution 設置為 true。

如果想通過代碼終止執行,可以使用 PlaygroundPage.current.finishExecution()

在 Xcode 中,還可以通過 PlaygroundPage.current.liveTouchBar 來自定義 Touchbar。

如何讓其他的類型實例在實時視圖中顯示

任何符合 PlaygroundLiveViewable 協議的類型,都可以被設置為實時視圖。

下面的代碼讓 UIBezierPath 可以直接在動態視圖中顯示

import PlaygroundSupport
import UIKit

let path = UIBezierPath()
var point = CGPoint(x: 0, y: 0)
path.move(to: point)
point = CGPoint(x: 100, y: 0)
path.addLine(to: point)
point = CGPoint(x: 200, y: 200)
path.addLine(to: point)

extension UIBezierPath: PlaygroundLiveViewable {
    public var playgroundLiveViewRepresentation: PlaygroundSupport.PlaygroundLiveViewRepresentation {
        let frame = self.bounds
        let view = UIView(frame: frame)
        view.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
        let layer = CAShapeLayer()
        layer.path = self.cgPath
        view.layer.mask = layer
        return .view(view)
    }
}

PlaygroundPage.current.liveView = path

獲取 Playground 的共享目錄 (playgroundSharedDataDirectory)

playgroundSharedDataDirectory 指向一個可以在 Playground Page 之間共享資源的目錄。

如果 Playground Page 被設置在 macOS 模式,該目錄中的內容可以在不同的 Playground 項目 macOS 模式的 Page 中共享。如果 Playground Page 運行在 iOS 模式,該目錄中的內容只能在同一個 Playground 項目的 iOS 模式的 Page 中共享(每個 Playground 項目都有各自對應的 iOS 模擬器,)。

import PlaygroundSupport
import AppKit

let url = playgroundSharedDataDirectory.appendingPathComponent("playground1.png")
if let data = try? Data(contentsOf: url) {
    _ = NSImage(data: data)
}

在 macOS 下,該目錄為用户文檔目錄下的 Shared Playground Data 子目錄。系統並不會自動創建該目錄,需要手動創建。

playgroundSharedDataDirectory 主要用於在 macOS 下保存多個 Playground 項目共同所需的數據。在單個 Playground 項目中,可以通過項目的 Resource 目錄在 Page 間共享數據。

總結

在【玩轉 Xcode Playground (下)】中,我們將着重介紹有關 SPM、資源管理、輔助代碼、文檔標註等方面的內容。

希望本文能夠對你有所幫助。