前言
本期是 Swift 編輯組整理週報的第三十九期,每個模塊已初步成型。各位讀者如果有好的提議,歡迎在文末留言。
Swift 週報在 GitHub 開源,歡迎提交 issue,投稿或推薦內容。目前計劃每兩週週一發佈,歡迎志同道合的朋友一起加入週報整理。
夢想之所以遙不可及,是因為今天的你和昨天一樣,並沒有拉近與夢想的距離。Swift社區陪你努力每一天,一同邁向象牙塔!👊👊👊
週報精選
新聞和社區:賣不動了 iPhone 在美國市場銷量或陷入停滯
提案:在導入聲明上使用訪問級別修飾符
Swift 論壇:討論從頭開始的基本 HTTP 客户端
推薦博文:用示例解釋了 Swift 中的值和類型參數包
話題討論:
如果公司允許遠程辦公但要降薪,薪資降多少可以接受?
上期話題結果
這個投票結果反映了人們在度過假期時的不同偏好,有些人喜歡冒險和旅行,有些人更願意宅在家裏,而還有一些人則追求休閒和享受。
新聞和社區
賣不動了 iPhone 在美國市場銷量或陷入停滯
10 月 8 日消息,作為蘋果公司總部的所在地,iPhone 在美國當地市場的銷量一直是很可觀的。之前就有報告披露 iPhone 在美國市場有 1.67 億用户,要比安卓系統用户的 1.44 億高出 2300 萬。
不僅如此,美洲市場向來也是蘋果主要的營收來源,常年在佔據在 40% 左右。蘋果的財報顯示,在截至 7 月 1 日的 2023 財年第三財季,營收的 817.97 億美元美洲市場貢獻了 353.83 億美元,遠高於歐洲等市場。而在第二財季 948.36 億美元的營收中,美洲市場則是貢獻了 377.84 億美元。
但是這一情況或將在明年有所改變,根據 Business Insider(BI)的第三輪專項年度調查數據顯示。由於經濟形勢的不確定以及手機更新週期延長,在美國市場許多用户將會繼續使用老款手機而不是升級到 iPhone 15,預計明年 iPhone 在美銷量或將陷入停滯。
不過該調查還強調,雖然 iPhone 的銷量會走低,但是蘋果公司的營收仍有望增加。這主要還是因為消費者更偏向購買價格更高的 Pro Max 系列,這也在一定程度上保證在面臨銷量壓力時能保持較高收入水平。
此外調查還發現,決定轉用安卓系統的用户佔比遠高於轉用蘋果的用户,也有很多用户表示計劃減少 iCloud 和 Apple TV+ 等增值服務。
據多家投行預測,iPhone 15 的出貨量將在 7000 萬到 8000 萬部之間,低於去年同期的 iPhone 14 的 9000 萬部以上。而根據 IDC 最新發布的數據,2023 年全球智能手機出貨量預計同比減少 4.7% 至 11.5 億部,創十年來新低。(來源:快科技)
與 Apple 專家會面交流
歡迎參加為全球開發者量身打造的各種講座、諮詢、實驗室等,與我們一起探索相關問題。
Apple 開發者活動為處於開發之旅各個階段的人士打造,全年以線上和線下的形式在世界各地舉行。無論你是希望提升現有的 App 或遊戲、完善設計還是啓動新項目,總有活動適合你。
申請設計一對一諮詢,2023 年 10 月 16 日 上午 10:00 – 下午 5:00 (GMT+8) ,30 分鐘線上諮詢,地點:Shanghai,與 Apple 專家遠程交流,共同探索如何設計美觀易用的出色 App。在時長 25 分鐘的線上諮詢中,你可以徵詢關於最新 UI/UX 設計原理、最佳實踐、設計模式等方面的建議。活動語言為普通話。
App 曝光度和營銷入門,2023 年 10 月 31 日 中午 11:00 – 中午 12:00 (GMT+8) ,線上講座,地點:Beijing,在這個線上講座中,瞭解如何提高你的 App 在 App Store 上的曝光度。探索人們如何在 App Store 上查找 App,瞭解優秀產品頁應包含的要素,並學習如何提升 App 的曝光度。我們還將探討搜索功能、推薦流量的作用以及可帶來更多下載次數的推廣功能。活動語言為普通話。
Apple Vision Pro 開發者實驗室 - 上海,2023 年 10 月 31 日 上午 9:30 – 2023 年 11 月 2 日 下午 5:00 (GMT+8),線下講座,地點:上海設計開發加速器。參加為期一天的開發者實驗室,體驗在 Apple Vision Pro 上運行的 visionOS、iPadOS 或 iOS App。你將能夠在 Apple 的幫助下測試、細化並優化你的 App 和遊戲,讓它們在無邊的空間畫布中提供卓越的體驗。由於場地有限,我們會審核每個請求,然後你會收到一封告知申請狀態的電子郵件。請務必詳細回答所有必填問題。
提案
通過的提案
SE-0409 在導入聲明上使用訪問級別修飾符 提案通過審查。該提案已在 三十八期週報 正在審查的提案模塊做了詳細介紹。
SE-0408 包迭代 提案通過審查。該提案已在 三十七期週報 正在審查的提案模塊做了詳細介紹。
Swift論壇
1) 討論Emitting 模塊花費的時間是 XCode 15.1 beta 的 25 倍
XCode 15.0 ( Swift 5.9.0.128.108 ) 和 XCode 15.1 Beta ( Swift 5.9.2.1.6 ) 之間的構建“發出模塊”階段似乎存在一些退化。
我的 XCWorkspace 中有幾個不同的框架和應用程序。 在 15.1 beta 中,大多數編譯速度都差不多,或者稍快一些。 然而,我們擁有的一個框架的時間似乎是以前的 10 倍以上。 當在 Xcode 時間線中查看時,幾乎所有時間都花在“Emitting Module”階段。
在乾淨的構建中,時間從約 56 秒縮短到約 1440 秒。 另外,有些文件的編譯速度似乎確實慢了一些,但這是一個很大的瓶頸,除了當時的“發射模塊”之外,時間線中沒有其他真正發生的事情。
如果我當時觀看 Activity Monitor,我的 CPU 的 swift-frontend 進程在此期間將保持在 100%。 但除此之外似乎沒有什麼可疑的。 比較輸出,框架的大小几乎相同,我沒有看到任何其他真正值得注意的東西。
關於什麼會導致這種巨大差異有什麼想法嗎?
回答
我剛剛發現並修復了 39 個案例,當模塊中包含大量 Swift 文件時,我們會看到這種情況發生。 (大量宏展開也可能發生)。對於我們看到迴歸的項目,“發射模塊”階段從 300 秒下降到 32 秒。
它很可能與您所看到的相同。 如果您能夠捕獲一個旋轉轉儲,我們可以使用旋轉轉儲來驗證這一點,或者如果您想嘗試的話,我們可以啓動工具鏈構建。
[編輯:對於那些好奇的人來説,編譯器有一個線性時間算法,可以從源位置的內部表示映射到該位置所在的源文件。 該算法“永遠”是線性時間的,但最近的錯誤修復將其置於熱路徑中。 解決方法是將其轉換為具有單元素最近使用的緩存的對數算法。]
2) 討論狀態檢查:Int128 和 UInt128
Swift 標準庫實際上包含 Int128 和 UInt128,它們只是沒有作為公共 API 公開。 它們是作為 SE-0329 的先決條件添加的:時鐘、即時和持續時間。 他們在公共 API 中的明顯缺席甚至在該提案的[第三次]審查期間被提出,但因超出範圍而被推遲。 多年來,一直有人對它們提出要求,甚至可以追溯到這些論壇存在之前。
swift-numerics(本質上)擁有自己的 128 位整數重新實現,現在基金會也正在考慮添加自己的。
更不用説各種第 3 方包,以及其他 Swift 庫和程序中這些類型的大量私有重新實現。
複製粘貼擴散這樣一個基本的數字類型似乎有點愚蠢,當它已經在標準庫中時,只需要發佈它即可。 不過,我懷疑這已經是實現這一目標的目標,所以我希望問題只是:預計到達時間?
回答
需要明確的是:Foundation 庫不考慮添加自己的。 他們建議使用 Numerics 現有的 DoubleWidth 測試支持來進行測試。 我們很快就會為 stdlib 推薦 Int128,但即使它可用,由於可用性限制(至少在中期),Foundation 和 Numerics 仍應使用雙寬度類型進行測試。 所以無論如何,這都是正確的前進道路。
3) 討論我是否必須手動檢查宏參數是否為文字?
在做了一些實驗來了解如何開發一個真正的宏之後,我遇到了一個問題:我試圖開發一個 @AddCompletionHandler 宏(如 WWDC 演講中提到的那樣),並且我嘗試將完成參數名稱傳遞為 宏的參數:
@attached(peer, names: overloaded)
public macro AddCompletionHandler(completionName: String = "onCompletion") = #externalMacro(...)
我應該得到一個字符串,我將使用它來構建要添加到函數簽名的新參數:
let completionParameter: FunctionParameterSyntax = "\(raw: completionParameterName): @escaping (\(returnType)) -> Void"
並在新生成的函數塊內調用完成:
let newCode: CodeBlockItemListSyntax = """
Task.detached {
await \(raw: completionParameterName)(\(functionDeclaration.wrappedInvocation))
}
"""
這樣做的問題是宏聲明接受任何返回字符串的表達式,因此您可以像這樣調用它:
@AddCompletionHandler(completionName: "on" + "Completion")
我發現自己手動檢查 AttributeSyntax 樹是否包含一個名為completionName 的參數,該表達式的類型為 StringLiteralExprSyntax,只有一個段,最後提取該值作為該唯一段的 .content.text。 如果這些步驟中的任何一個失敗,我都會發出一條診斷消息,要求該值是一個文字。
這是應該如何工作的嗎? 對於看似常見的用例來説,這似乎是一個極其繁瑣的過程。 我在這裏錯過了什麼嗎?
回答
這是實現它的一種迂迴方式,但您可以執行以下操作:
定義符合 ExpressibleByStringLiteral 的自定義類型,並使用該類型作為宏的參數而不是 String。 用户仍然可以將字符串文字直接傳遞給宏調用,但他們無法執行任何接近但不是文字的操作,例如 “hello”+“world”。
但是,這仍然會讓有人這樣做,這是你不希望的,因為你無法評估 x:
let x: YourCustomType = "onCompletion"
@AddCompletionHandler(completionName: x)
因此,下一個技巧是,當您在自定義類型中實現 init(stringLiteral:) 時,只需將其設置為 fatalError() 即可。 這將阻止任何人嘗試創建它的實例並將其存儲在某個地方。 但該類型在宏使用中仍然有效,因為在宏調用中使用宏時,該類型實際上並不調用 init(stringLiteral:) 。 它所要做的就是類型檢查它是否有效,確實如此。 (如果有人確實嘗試在某處創建顯式實例,則直到運行時才會捕獲該錯誤。)
使這變得更容易的是某種參數必須為常量的功能,這些功能之前已經在這些論壇上討論過。
僅當用户嘗試直接實例化新類型時才會發生運行時錯誤,否則除了在宏簽名中命名之外,該新類型對他們是隱藏的。 將其命名為 CompletionHandlerNameLiteral 之類的名稱,這樣就不會混淆其用途。
沒有編譯時失敗被轉移到運行時,因為它嚴格阻止了編譯器以前允許的使用:現在編譯器不再允許像“hello”+“world”這樣的表達式並要求宏檢查它,而是 編譯器會停止它,宏不再需要檢查它。
這不是一個完美的解決方案,但我可以理解,用户並不都希望為“這是一個文字嗎”之類的事情編寫相同的檢查,因此最好讓編譯器在可能的情況下為您完成工作。 在缺乏 const-value 強制功能的情況下,SwiftSyntax 將成為此類輔助 API 的良好家園,至少可以減輕負擔。
4) 討論從頭開始的基本 HTTP 客户端
我想使用 Swift 從頭開始創建基本的 HTTP 客户端,以達到學習目的,以瞭解互聯網上 http 的發送者和接收者是如何工作的。 這只是出於原始學習目的,所以我只想使用套接字。 現在我正在本地主機中嘗試,我在SO 1中發佈了相同的內容。我嘗試了下面的代碼:
import Foundation
import Darwin
final class Client {
let host: String
let port: UInt16
init(host: String, port: UInt16) {
self.host = host
self.port = port
}
func get(_ completionHandler: @escaping() -> Void) {
// Create a socket
let sock = socket(AF_INET, SOCK_STREAM, 0)
var serveraddr = sockaddr_in()
serveraddr.sin_family = sa_family_t(AF_INET)
serveraddr.sin_port = self.port
serveraddr.sin_addr.s_addr = inet_addr(self.host)
// Connect to the server
print("Connecting....")
withUnsafePointer(to: &serveraddr) { sockaddrInPtr in
let sockaddrPtr = UnsafeRawPointer(sockaddrInPtr).assumingMemoryBound(to: sockaddr.self)
let connection = connect(sock, sockaddrPtr, socklen_t(MemoryLayout<sockaddr_in>.size))
if connection == 0 {
// Form an HTTP GET request
let request = "GET / HTTP/1.1\r\nHost: \(self.host)\r\n\r\n"
let bytes = [UInt8](request.data(using: .utf8)!)
// Send the request over the socket
send(sock, bytes, request.count, 0)
/*
// Receive and print the response
var response = [UInt8](repeating: 0, count: 1024)
let bytesRead = recv(sock, &response, response.count, 0)
if bytesRead > 0 {
if let httpResponse = String(bytes: response, encoding: .utf8) {
print(httpResponse)
}
}
// Close the socket
close(sock)
*/
} else {
print("not connected \(connection)")
}
}
}
}
使用:
let client: Client = .init(host: "127.0.0.1", port: 8080)
client.get {
print("GET request successfully executed!")
}
我使用 python3 -m http.server 8080 作為包含一些文件的文件夾上的測試服務器。 它適用於瀏覽器和郵遞員 GET 請求。
但問題是 cleint 退出,打印未連接 -1
我該如何解決此問題併成功請求?
回答
從 Swift 正確使用 BSD 套接字是一個嚴峻的挑戰。 我自己在這個問題上反覆討論了很多次,最終選擇了從 Swift 調用 BSD 套接字中所示的方法。 正如那篇頂級文章中所解釋的,這並不適用於生產代碼,而是適用於我們在這裏討論的測試項目。
至於您是否應該使用 BSD 套接字,這是我在 TN3151 選擇正確的網絡 API 中介紹的內容。
哦,實現一個正確的 HTTP 客户端非常困難,即使您將自己限制為 HTTP/1.1 而沒有 HTTPS。 因此,雖然為這樣的測試項目編寫自己的 HTTP 代碼很好,但如果您打算部署它,我建議您使用現有的 HTTP 庫。
需要明確的是,ATS 僅適用於 URLSession 及以上版本。 低級 API,如網絡框架和 BSD 套接字,只是忽略 ATS。
應用程序沙箱適用於所有網絡連接,因此這是正確的舉措(-:
5) 討論不同平台不同的宏實現
我正在嘗試創建一個宏,允許我在資源包中按名稱引用顏色。
例如,能夠執行以下操作:
let myColor = #color("MyColor")
在 macOS 上,我希望將其為:
"NSColor(named: \(argument)) ?? NSColor.clear"
在 iOS 上,我希望它是:
"UIColor(named: \(argument))"
我寫了以下宏:
public struct ColorMacro: ExpressionMacro {
public static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> ExprSyntax {
guard let argument = node.argumentList.first?.expression else {
throw MacroError.invalidArguments
}
#if canImport(AppKit)
return "NSColor(named: \(argument)) ?? NSColor.clear"
#elseif canImport(UIKit)
return "UIColor(named: \(argument))"
#else
#error("Unsupported platform")
#endif
}
}
和
#if canImport(AppKit)
import AppKit
@freestanding(expression)
public macro color(_ named: String) -> NSColor = #externalMacro(module: "SwatchbookMacrosMacros", type: "ColorMacro")
#elseif canImport(UIKit)
import UIKit
@freestanding(expression)
public macro color(_ named: String) -> UIColor = #externalMacro(module: "SwatchbookMacrosMacros", type: "ColorMacro")
#endif
在為 macOS 構建時效果很好,但在為 iOS 構建的目標中使用時,它似乎仍在嘗試使用 AppKit 分支並引用 NSColor。
難道我做錯了什麼? 是否使用正在構建的平台來確定可用性,而不是目標平台?
回答
這裏的問題是 #if 塊是 IfConfigDeclSyntax,而不是表達式。 不過,您可以將整個事情包裝在立即執行的閉包中,使其成為一個表達式:
return """
{
#if canImport(AppKit)
NSColor(named: \(argument)) ?? NSColor.clear
#elseif canImport(UIKit)
UIColor(named: \(argument))
#else
#error("Unsupported platform")
#endif
}()
"""
但如果您打算執行所有這些操作,那麼在宏的庫目標中創建一個輔助函數並調用它可能會更乾淨、更簡單:
func __colorHelper(_ name: String) {
#if canImport(AppKit)
NSColor(named: name) ?? NSColor.clear
#elseif canImport(UIKit)
UIColor(named: name)
#else
#error("Unsupported platform")
#endif
}
並讓您的宏實現調用該函數:
return """
YourModuleName.__colorHelper(\(argument))
"""
推薦博文
Swift 5.9 中的調試改進
摘要: 本篇官方文章介紹了 Swift 5.9 在編譯器和 LLDB 調試器中引入了許多新的調試功能。以下是三個改變,可以幫助你在日常調試工作中更加便捷。首先,通過使用快捷命令別名 p 和 po 進行變量檢查可以更快速地進行操作。
其次,LLDB 的 dwim-print 命令提供了更加用户友好的方式來打印變量。而且,在 Swift 5.9中,使用 p 命令不再會創建像 $R0 這樣的持久結果變量,這些變量常常在調試會話中未被使用。最後, LLDB 現在支持在表達式評估中使用泛型類型參數,這使得在調試過程中能更好地區分不同的變量。Swift 5.9 還引入了更精確的詞法作用域信息,使得調試器能夠更好地區分不同的變量。
用示例解釋了 Swift 中的值和類型參數包
摘要: 本文介紹了 Swift 中的值和類型參數包,並結合示例進行了詳細解釋。類型參數包和值參數包允許你編寫一個接受任意數量具有不同類型參數的通用函數。在 Swift 5.9 中,由於 SE-393、SE-398 和SE-399 的提案,這一新特性得以實現。
採用參數包的最顯著影響之一是在 SwiftUI 中的10個視圖限制已經不存在,這是由於在這些提案之後可實現了可變參數泛型。本文還解釋了參數包的解決方案,它們幫助我們編寫可重用的代碼,避免編寫大量的函數重載。從 Swift 5.9 開始,我們可以使用參數包重寫類似的函數。本文末尾總結了參數包的優勢,並提供了進一步學習 Swift 的資源鏈接。
SwiftData 中的併發編程
摘要: 本文介紹了在 SwiftData 中進行併發編程的方法。儘管在 Core Data 中進行併發編程可能有一些陷阱,但 SwiftData 作為 Core Data 的繼任者提供了更加安全和優雅的併發編程機制。文章討論瞭如何使用串行隊列來避免數據競爭問題,如何創建使用私有隊列的 ModelContext ,以及如何使用 Actor 實現更優雅的併發編程。
此外,還介紹了通過 PersistentIdentifier 來獲取數據以及在非主線程中修改主線程上的對象屬性的方法。通過深入瞭解 SwiftData 的併發編程特性,開發者可以提高代碼的安全性和可讀性。
話題討論
如果公司允許遠程辦公但要降薪,薪資降多少可以接受?
- 10%以下
- 25%以下
- 50% 以下
- 保持,不接受降薪
- 應該加薪,工作量增加
歡迎在文末留言參與討論。
關於我們
Swift社區是由 Swift 愛好者共同維護的公益組織,我們在國內以微信公眾號的運營為主,我們會分享以 Swift實戰、SwiftUl、Swift基礎為核心的技術內容,也整理收集優秀的學習資料。
特別感謝 Swift社區 編輯部的每一位編輯,感謝大家的辛苦付出,為 Swift社區 提供優質內容,為 Swift 語言的發展貢獻自己的力量。