Stories

Detail Return Return

蘋果已提供新的設計資源 | Swift 週報 issue 32 - Stories Detail

前言

本期是 Swift 編輯組自主整理週報的第二十三期,每個模塊已初步成型。各位讀者如果有好的提議,歡迎在文末留言。

Swift 週報在 GitHub 開源,歡迎提交 issue,投稿或推薦內容。目前計劃每兩週週一發佈,歡迎志同道合的朋友一起加入週報整理。

騷年,努力去成為你期待已久的自己吧,就像Swift社區一樣,時刻心懷夢想,不斷向前!👊👊👊

週報精選

新聞和社區:蘋果已提供新的設計資源

提案:本期提案沒有最新內容

Swift 論壇:討論 Non-Reentrant Actors

推薦博文:AngularGradient 在 swiftUI 中的使用

話題討論:

你認為 vision pro 是否會加速虛擬現實技術的發展?

上期話題結果

根據投票結果,小編認為企業應根據自身情況和員工需求,平衡薪資保密與透明的利弊,制定適合的薪資政策。

新聞和社區

現已提供新的設計資源

為方便開發者在 Apple 平台上構建 App,我們現在提供了全新及更新後的設計資源,讓你可以更便捷、更準確地設計 App。

visionOS 設計資料庫以及適用於 Figma 和 Sketch 的模板 (英文)。

適用於 Figma 和 Sketch 的 iOS 17 和 iPadOS 17 設計套件 (英文)。

適用於 Figma 和 Sketch 的 macOS Sonoma 設計套件 (英文)。

適用於 Sketch 的 watchOS 10 設計套件 (英文)。

SF Symbols 5 Beta 版,包含 700 多個新符號 (英文)。

更新後的《人機界面指南》(英文),現提供簡體中文和日文版本。

SF 腳本擴展,現已支持亞美尼亞語、格魯吉亞語和希伯來語 (英文頁面)。

visionOS SDK 現已發佈

Apple Vision Pro 提供了一幅無邊的畫布,你現在就可以開始為這幅畫布打造前沿的空間計算 App。下載 Xcode 15 Beta 版 2,其中包含 visionOS SDK 和 Reality Composer Pro — 這是一種新的工具,可讓你輕鬆地預覽和準備適用於 visionOS 的 3D 內容。將 visionOS 目標添加到你的現有項目中或構建一個全新的 App,然後在 Xcode 預覽中迭代你的 App。你可以在全新的 visionOS 模擬器中與你的 App 互動,探索各種房間佈局和光線條件,並創建測試和可視化效果。此外,我們還提供了新的文檔和示例代碼,幫助你完成整個開發過程。

提案

本期提案沒有最新內容,期待下期更新~~

Swift論壇

1) 提議使用部分不可複製類型的字段

介紹

當前給定一個類似於 var 的構造(例如:var、inout),Swift 不允許部使用耗該類型的存儲字段:

struct E : ~Copyable {}

struct S : ~Copyable {
    var first: E
    var second: Klass
}

var s = S()
let _ = s.e // Error! Cannot partially consume s

不可複製類型的部分使用

在設計空間中考慮以下幾個不同的軸:

  1. 關於帶有 deinit 的類型
  2. 啓用 Library Evolution 時
  3. 當 Library Evolution 被禁用時
  4. 無論哪種情況,是否應該只允許方法中的部分消耗。

具有 Deinits 的類型的部分消耗

禁止使用 deinits 部分消耗不可複製類型,因為當字段被部分消耗,允許該類型被銷燬

例如:

struct E : ~Copyable
struct S : ~Copyable {
   var first: E
   var second: E
   deinit {}
}

var s = S()
let _ = s.first // s.first is destroyed here
doSomething()
// s.second is destroyed here

由於這裏的 s 已被部分消耗,因此永遠不會將其全部銷燬,這意味着永遠不會調用它自己的 deinit,這意味着不能允許發生。

請注意,即使不允許這樣做,仍然允許使用方法的作者使用丟棄運算符來關閉值的 deinit,然後部分解構該值。

2) 提問一組弱引用可以符合Collection嗎?

問題陳述:

有一個收集弱引用的類型,可以對其進行迭代並追加。

希望它符合 RangeReplaceableCollection,但是不能做任何比 Sequence 更具體的事情。

該類型本身是一個經典的指針長度容量三元組,其中指針指向弱引用緩衝區。 一旦長度==容量,在嘗試重新分配之前,會掃描緩衝區以查找可以丟棄的 nils。 僅當無法刪除足夠的 nil 來為新元素騰出空間時,才會執行重新分配。

使其符合 Collection 的問題是下標(_:)。 如果索引類型只是緩衝區中的索引,則其他線程可能會導致弱引用從下面刪除,因此索引可能會變得無效,而不會對集合進行明顯的更改。

可以想到兩種方法來解決這個問題,但都不能令人滿意。

第一個是使索引類型也持有對該對象的強引用。 但是擔心當用户沒有意識到他們通過索引持有強大的參考時,可能會產生問題。

第二個是使元素類型為T? 而不是 T。這是一種誤導,因為迭代器會跳過 nils,但會使下標可實現。

第三個選項是在不實際遵守協議的情況下實現許多(但不是全部)收集操作。 擔心這是我必須做的,除非能證明其他兩種行為之一是合理的。

有沒有一種方法可以在不改變類型語義的情況下實現協議?

回答:

不是集合似乎是所提供的數據結構的固有屬性,而不是實現限制。 如果序列中的第 n 個項目可以從 x 更改為 y,因為 x(或序列中較早的某個其他對象)已被收集,則序列沒有穩定的索引。

3) 提問swift Macro 中沒有這樣的模塊“UIKit”

在 swift Macro 中導入 UIKit 時,報錯 No such module 'UIKit' 。

宏包有以下平台

平台:[.macOS(.v10_15)、.iOS(.v13)、.tvOS(.v13)、.watchOS(.v6)、.macCatalyst(.v13)]

回答:

在構建過程中,宏在編碼的計算機(可能是 Mac/Windows/Linux)上運行。 它不在 iOS 上運行,因此無法訪問 UIKit。

為什麼在宏中需要 UIKit(而不是在聲明宏的包中)?

可以嘗試創建一個可以導入 UIKit 的“Mac Catalyst”宏,但即使有可能,也可能沒有用

4) 提議低級聯動控制屬性:@used 和@section

動機

動機有兩個目標:

提供低級構建塊來構建更多高級 API,例如 “鏈接器集”(見下文)或自定義每種類型元數據,如 SE-0385 中所述(swift-evolution/proposals/0385-custom-reflection-metadata.md,位於 main·apple/swift-evolution·GitHub 2)。

儘管這個推介/提案實際上並沒有嘗試添加或設計高級 API,只是提供了一條單獨解鎖設計的路徑。

為系統編程用例提供低級機制(這些用例是針對具體系統的定製案例),並構建一個通常可重用的高級 API 是沒有意義的(項目作者可以自由地構建這樣一個高級 API,例如項目的內部機制)。

“鏈接器集”機制是 Swift 已經在使用的一種方法:幾乎任何類型的編譯器發出的元數據都被放入二進制文件中專門命名的部分中,並給出固定佈局的記錄。

然後,想要查找某些信息時(例如,在二進制文件中查找協議一致性),要求加載器(Darwin 上的 dyld)為我們提供每個加載的該部分的起始/結束地址。 圖像,然後可以迭代這些部分中的所有記錄。

還可以從進程外部提取一些元數據,或者從二進制文件本身中挖掘它。使用現有的反射庫來完成此操作,例如 swift-inspectswift-reflection-dump

在 Swift 語言中添加功能來表達該機制的第一部分:將固定佈局記錄放入專門命名的部分。

提議

其中一些已經在功能標誌下實現為 main 中的下劃線屬性( @_section@_used),通過 https://github.com/apple/swift/pull/65901 實現。 總之:

@used 屬性,通過 llvm.used 將全局變量或頂級函數標記為“不要死區”,大致相當於 C/C++ 中的 __attribute__((used)) 。

@section("...") 屬性,將全局變量或頂級函數放入具有該名稱的節中,大致相當於 C/C++ 中的 __attribute__((section("..."))) 。

這些註釋只能應用於保證最終“靜態初始化”(而不是通過 init_once 運行時調用延遲初始化)的全局變量,因為否則註釋沒有任何意義。

這就提出了一個有趣的問題:當用於初始化全局時,哪些表達式可以保證“靜態初始化”? 建議從一組非常基本的表達式開始,並在將來對其進行改進。

強制優化管道已經使整數文字、元組和簡單算術表達式進行“靜態初始化”,如果存在任何具有 @section 屬性的全局變量,可以在 SIL 管道末尾明確拒絕編譯 這不是靜態初始化的。

然後,作為後續改進,應該考慮允許 POD 結構類型也在強制優化管道中處理,並允許與 @section 一起使用。

雖然超出了本次推介的範圍,但以下是“鏈接器集”API 的運行時端的草圖:

 // in Module1
@used @section("__DATA,mysection") private let my_entry: Int = ...

// in Module2
@used @section("__DATA,mysection") private let my_entry: Int = ...

for entry in SwiftRuntime.section("__DATA,mysection", as: Int.self) { // this uses the loader's APIs to locate and iterate over the section
  ...
}

最終,將其包裝到基於宏的解決方案中可能是有意義的,這樣就根本不會公開低級屬性:

@LinkerSet(name: "myLinkerSet") private let myEntry: Int = 42

for entry in SwiftRuntime.linkerSet("myLinkerSet", as: Int.self) {
  ...
}

或者,在想要將元數據附加到類型的情況下(由 SE-0385 推動):

@Registered(name: "My Favorite Type") // this creates a hidden global in a named section
class MyType { }

for regType in allRegisteredTypes { // queries over the entries in the section
  ...
}

5) 提問在構建期間啓用預處理器標誌

有一個 C++ 頭文件,僅在設置了預處理器標誌時才公開一個類:

#ifdef UNIX_ENABLED

class Some_Class {
...
}
#endif // UNIX_ENABLED

當調用 swift 編譯器時:

swiftc MyApp.swift -cxx-互操作性模式=默認-Xcc -std=c++17 -I cxx -c -parse-as-library

並嘗試在 MyApp.swift 中使用 Some_Class ,但顯然找不到該類。 嘗試使用 -D UNIX_ENABLED 但這沒有幫助。

有什麼想法可以進行此編譯嗎?

回答:

可以嘗試將 -Xcc -D -Xcc UNIX_ENABLED 傳遞給 swiftc 以確保它將 -D 轉發給 clang

6) 討論Non-Reentrant Actors

每當編寫涉及 Actor 的代碼時,發現自己想要對 Actor 進行有意義的工作,但最終會在此過程中引入難以捕獲的錯誤。 以這個簡單的例子為例:

actor HeavyLifting {
    var cachedResult: String?
    func doHeavyLifting() async throws -> String {
        if let cachedResult { return cachedResult }
        let result = try await // load some resource and process it.
        cachedResult = result
        return result
    }
}

開發人員可能會認為這是確保只執行一次“繁重工作”並緩存結果的完全足夠的方法。然而,更精明的審閲者可能會注意到,雖然這不會導致災難性的失敗,實際上也不會保護繁重的工作不被多次完成,因為對此方法的多個併發請求雖然不是“ 一旦達到每個負載的暫停點,每個負載就會開始繁重的工作負載。

更具冒險精神的開發人員的工具帶中確實有一個工具可以解決這個問題 - 非結構化任務:

actor HeavyLifting {
    var heavyLiftingTask: Task<String, Error>?
    var heavyLiftingResult: String {
        get async throws {
            if let heavyLiftingTask { return try await heavyLiftingTask.value }
            let task = Task { try await // load some resource and process it. }
            heavyLiftingTask = task
            return try await task.value
        }
    }
}

現在引入了不完整樣板的微妙平衡,更不用説缺乏對取消傳播的適當支持,所有這些都是以防止死鎖的名義,而實際上並沒有阻止富有冒險精神的開發人員編寫可能會導致死鎖的代碼。

代碼中到處都是這樣的樣板文件,最終肯定會弄錯,當一天結束時,希望並假設在錯誤的等待最終潛入之前所做的事情,是為了 Actor 的方法在進行過程中不可調用,而 Actor 的其餘部分則繼續其業務。

已經使用過 Actor 一段時間,重新審視不可重入性,因為它是比較有用的工具。可以看到單獨的方法或函數是希望以某種能力強制執行串行訪問的東西,但是也可以看到將其應用於對整個參與者的所有訪問的好處。

回答:

在未來方向(有任務鏈重入的獎勵積分,但沒有任務鏈重入絕對非常有用)將如那裏所描述的那樣非常棒。不希望應用於整個 Actor。

會有助於避免現在很容易變得脆弱的模式來解決缺乏此功能的問題。

可選的參與者“發送”對 Void 返回函數的支持一起將釋放參與者的很大一部分潛力。

7) 討論宏擴展後訪問源代碼

當構建一個使用 SwiftSyntax 遍歷 Swift 源代碼的工具時,是否有一種直接的方法來遍歷宏擴展後的源代碼?

這是否需要通過嘗試擴展每個源文件來手動完成?

正在構建的工具中,複製源文件並對其進行操作,但這是直接來自文件系統和預擴展的。 具體來説,該工具會遍歷有效的 .swift 文件並捕獲符合給定協議的所有類型。使用宏來添加對該協議的一致性,但是構建插件無法僅通過閲讀非擴展源代碼來了解最終的一致性。 使用 Target.directory 來確定給定目標的源文件所在的位置,但是是否有更好的方法來訪問擴展源所在的構建目錄?

回答:

如果對宏擴展的工作原理感到好奇,可以在這個文檔中閲讀。

Swift 中的宏擴展基於語法的內存表示,這意味着無法在不手動執行擴展的情況下直接從源文件中檢索擴展的代碼。

可能會考慮嘗試使用此方法擴展所有宏:SyntaxProtocol.expand(macros:in:)

推薦博文

AngularGradient 在swiftUI中的使用

摘要: 本篇文章講解了如何在 swiftUI 中使用 AngularGradient,用於從一種顏色過渡到另一種顏色,可選地通過圍繞指定中心點的放射狀圖案中的一系列顏色。本文探討了設置不同中心點以及指定漸變的起始角度和結束角度範圍的效果。 AngularGradient 可用於在 SwiftUI 視圖中創建引人注目的視覺效果,尤其是在圓形或弧形中使用時。

字節跳動 DanceCC 工具鏈系列之Swift 調試性能的優化方案

摘要: 本篇文章講解了大型 Swift 項目如何通過開關,以及自定義 LLDB ,優化 Swift 開發同學的調試速度,提高整體的研發效能。其中講解了 LLDB 的部分工作流程,以及針對性優化的技術細節,以及實際效果。

Swift 的可選值優化

摘要: 在 Swift 中,nil 的語義與 Objective-C 中的 nil 不同,它代表沒有值的概念。為了表示沒有值,Swift 引入了 nil 關鍵字,但在內存中的表示方式與 Objective-C 不同。文章通過代碼展示了 nil 在內存中的真正表示,發現可選的 Int? 類型比普通的 Int 類型多佔一個字節,用來表示是否有值。然而,Swift 編譯器已經進行了優化,例如 Bool? 類型只佔用一個字節,用2來表示 nil 。 String 類型也可以在內存中用0表示沒有值。對於 Class 類型和 Enum 類型,空指針或越界值可以表示沒有值,也沒有內存浪費。總之, Swift 編譯器會盡可能地優化可選值的內存佔用,但仍建議在某些情況下儘量少使用可選值,特別是在結構體中連續多個可選的 Int 的情況下,可以使用非可選值並用0初始化它們。

話題討論

你認為vision pro是否會加速虛擬現實技術的發展?

  1. 不會
  2. AI發展才是王道

歡迎在文末留言參與討論。

關於我們

Swift社區是由 Swift 愛好者共同維護的公益組織,我們在國內以微信公眾號的運營為主,我們會分享以 Swift實戰SwiftUlSwift基礎為核心的技術內容,也整理收集優秀的學習資料。

特別感謝 Swift社區 編輯部的每一位編輯,感謝大家的辛苦付出,為 Swift社區 提供優質內容,為 Swift 語言的發展貢獻自己的力量。

Add a new Comments

Some HTML is okay.