前言
本期是 Swift 編輯組整理週報的第三十六期,每個模組已初步成型。各位讀者如果有好的提議,歡迎在文末留言。
Swift 週報在 GitHub 開源,歡迎提交 issue,投稿或推薦內容。目前計劃每兩週週一釋出,歡迎志同道合的朋友一起加入週報整理。
一米陽光下陰雨綿綿,一米陽光上晴空萬里,這就是生活。Swift社群伴你一起,走過風雨,沐浴暖陽!👊👊👊
週報精選
新聞和社群:iPhone 15 全系配 USB-C 蘋果拒絕介面和安卓互通
提案:對 AsyncStream 的 Backpressure 支援
Swift 論壇:提議全域性變數的嚴格併發
推薦博文:WWDC23 10105 - 打造響應更快的相機體驗
話題討論:
日本核汙水排海,你還會吃海鮮嗎?
上期話題結果
根據投票結果可以看出,大家有不同的想法。小編認為家長應該根據孩子的個性特點和興趣愛好靈活調整,注重培養他們的創新精神和獨立思考能力。
新聞和社群
訊息稱蘋果公司和印度財政部官員磋商,擴大在印度的製造產能
8 月 25 日訊息,根據印度當地媒體 Business Today 報導,蘋果印度地區的高管已經和當地財政部會面,探討擴大 iPhone 在印度產能相關事宜。
訊息稱本次探討涉及面很廣,印度政府希望以蘋果公司帶頭下,進一步推動本土智慧手機制造業,並磋商相關的扶持政策。
而蘋果公司在交流中也表示,印度是非常重要的生產地和消費市場,樂於擴大在印度市場的產能,但希望能獲得更多的補貼,以及政策福利。
IT之家此前報導,根據市場調查機構 Counterpoint Research 公佈的統計資料,2023 年第 2 季度印度市場營收表現首次超過法國和德國,成為蘋果第五大 iPhone 市場。
蘋果 iPhone 零售業在印度市場的快速增長的同時,蘋果也加快了 iPhone 在印度製造的腳步。蘋果加大了在印度市場的投資力度,希望供應鏈實現多元化發展。(來源:IT之家)
iPhone 15 Pro 機型新增泰坦灰
新渲染圖曝光,訊息稱蘋果 iPhone 15 Pro 機型泰坦灰將替代金色 iPhone15Pro新增灰色。8 月 25 日訊息,根據國外科技媒體 9to5Mac 報導,蘋果今年將調整 iPhone 15 Pro 和 iPhone 15 Pro Max 兩款機型的顏色選項,取消金色,新增灰色。
在最新報導稱這種全新灰色官方名稱為“泰坦灰”(Titan Gray),並分享了這種顏色的概念渲染圖,可以看到“泰坦灰”顏色要比現有的銀色 / 白色更深一些,但比深空黑要更淡一些。
該媒體還透露蘋果今年推出的 iPhone 15 Pro 機型將會取消暗紫色,並由深藍色替代。
注: iPhone 14 Pro 機型共有暗紫色、金色、銀色和深空黑四種顏色。
蘋果預估將於 9 月 12 日釋出 iPhone 15 系列,其中 iPhone 15 系列標準版將有黑色,綠色,藍色,黃色和粉紅色。(來源:IT之家)
iPhone 15 全系配 USB-C 蘋果拒絕介面和安卓互通
8月19日訊息,據供應鏈最新訊息稱,iPhone 15 全系將會配備 USB-C 介面,不過不同型號會被差異對待。
按照供應鏈說法,iPhone 15 標準版只是配備普通版本的 USB-C 介面,而 iPhone 15 Pro 系列則在速度上有明顯的提升,同時這個功能只能在 MFi 認證的 USB 資料線下發揮功效。
具體來說就是,iPhone 15 標準版提供 USB 2.0 版本,傳輸速度最高 480 Mbps,與之前的 Lightning 介面差不多。而 iPhone 15 Pro 系列用的是 USB 3.2,傳輸速度能達到 20 Gbps,比標準版快 20 倍以上。
對於消費者來說,這將是多年來 iPhone 系列手機最大的改進之一。配備該埠後,iPhone 使用者在旅行時不再需要為手機和其他移動裝置攜帶兩根不同的充電線,不過想法是好的,但蘋果卻不會這麼幹。
按照供應鏈配件商的說法,蘋果不會讓 iPhone 15 的 USB-C 介面與安卓通用,即便是有違法的行為,但依然會如此做,畢竟 MFi 認證背後一年是幾十億美元的盈利。
此外,從蘋果的備貨來看,Pro 系列佔比超過 60%,他們也是想從這些細節的地方卡位使用者,讓大家主動去買高價版本。
提案
正在審查的提案
SE-0407 成員 Macro 一致性 提案正在審查。
SE-0402中從一致性宏到擴充套件宏的轉變包括擴充套件宏能夠了解型別已經遵循了哪些協議(例如,因為遵循了超類或在某處宣告瞭顯式一致性),這樣宏就可以避免新增不需要的宣告和一致性。這也意味著新增的任何新宣告都是擴充套件的一部分——而不是原始型別定義的一部分——這通常是有益的,因為這意味著(例如)新的初始化器不會抑制成員初始化器。將協議一致性拆分為各自的擴充套件通常也被認為是一種很好的形式。
然而,有時用於一致性的成員確實需要成為原始型別定義的一部分。例如:
- 非 final 類中的初始化項必須是必需的初始化項,以滿足協議要求。
- 非 final 類的可重寫成員。
- 儲存的屬性或大小寫只能在主型別定義中。
對於這些情況,成員宏可以生成宣告。然而,成員宏並沒有提供任何關於應該為哪種協議一致性提供成員的資訊,因此宏可能會錯誤地嘗試將一致性成員新增到已經符合協議的型別中(例如,透過超類)。這可能使某些宏(例如實現 Encodable
或 Decodable
協議的宏)無法實現。
SE-0406 對 AsyncStream 的 Backpressure 支援 提案正在審查。
SE-0314引入了新的 Async[Throwing]Stream
型別,作為根非同步序列。這兩種型別允許從同步回撥(如委託)橋接到非同步序列。該提案增加了一種構建非同步流的新方法,目的是將 Backpressure 系統橋接成非同步序列。此外,該提案旨在澄清消費任務取消和生產方表示終止時的取消行為。
Swift論壇
1) 提議宏文字協議
目前僅允許在頂層使用宏。 然而,在某些情況下,巢狀宏會很有好處。
例如,我們可以新增具有宏要求的新文字協議:
public protocol ExpressibleByMacroIntegerLiteral {
associatedtype IntegerLiteralType: _ExpressibleByBuiltinIntegerLiteral
@freestanding(expression)
macro Init(integerLiteral: IntegerLiteralType) -> Self
}
然後用編譯器的魔法,醬子:
let x: Decimal = 5.3
可以變成醬子:
let x = #Decimal.Init(integerLiteral: 5.3)
然後將擴大
我知道宏的設計目標之一是避免這種不可見的宏使用,但是已經有很多編譯器魔法可以透過 _ 文字協議來表達,這將使它們更加通用。
例如,當前如果型別是 ExpressibleByStringLiteral
但只有某些字串文字有效,則唯一的選擇是在執行時遇到無效字串文字時捕獲。 這違背了文字的編譯時性質,而文字應該允許檢查文字。 在基金會提出將 URL 改為 ExpressibleByStringLiteral 時,這個問題在某種程度上被掩蓋了,但已經完全解決了。
這個語句:
downloadFile(at: "https://apple.com")
看起來比這個好很多:
downloadFile(at: #URL("https://apple.com"))
2) 提議巢狀 if let 和 guard let
介紹
在 Swift 中,if let 語句通常用於可選的解包。 它透過處理可選值幫助開發人員編寫更乾淨、更安全的程式碼。
目前,if let 語句解包單個可選值。 然而,在某些情況下,我們希望以更簡潔的方式解開巢狀物件的可選屬性。
該提案建議擴充套件 if let 和 Guard let 語句以支援巢狀可選展開。
1. if let 巢狀
巢狀 if let 的擬議語法將允許開發人員有條件地解包巢狀物件的可選屬性。 如下:
if let myOptionalObject?.optionalValue {
// 'optionalValue' is now safely unwrapped and ready to use // *1
} else {
// Handle the case where 'optionalValue' is nil
}
2. 巢狀的 guard let
類似地,所提議的巢狀 Guard Let 語法將允許開發人員有條件地解開巢狀物件的可選屬性。 如下:
guard let myOptionalObject?.optionalValue else {
// Handle the case where 'myOptionalObject' or 'optionalValue' is nil
// This could include returning from the current function, loop, or throwing an error
}
// 'optionalValue' is now safely unwrapped and ready to use // *1
3) 提議全域性變數的嚴格併發
介紹
該提案定義了無資料競爭的全域性變數的使用選項。 在此提案中,全域性變數包含靜態持續時間的任何儲存:在全域性範圍內宣告或作為靜態成員變數宣告的 let 和儲存變數。
動機
全域性狀態在併發性中提出了挑戰,因為它是可以從任何程式上下文訪問的記憶體。 全域性變數在資料隔離檢查中受到特別關注,因為它們違背了其他強制隔離的嘗試。
本地且未捕獲的變數只能從本地上下文訪問,這隱式地隔離了它們。 值型別的儲存屬性已經透過排他性規則隔離。
可以透過使用可傳送性強制或使用參與者限制來隔離引用型別的包含物件,從而隔離引用型別的儲存屬性。 但全域性變數可以從任何地方訪問,所以這些工具不起作用。
建議的解決方案
在嚴格的併發檢查下,要求每個全域性變數要麼與全域性參與者隔離,要麼兩者都隔離:
- 不可變的(immutable)
- 可傳送型別(Sendable)
immutable 並且 Sendable 的全域性變數可以從任何上下文安全地訪問,否則需要隔離。
詳細設計
這些要求可以在宣告時在型別檢查器中強制執行。
源相容性
由於增加了限制,因此在使用嚴格的併發檢查時可能需要更改某些型別宣告。 然而,此類原始碼更改仍然向後相容任何具有併發功能的 Swift 版本。
ABI相容性
該提案本身不會新增或影響 ABI(Application Binary Interface),但是它可能對採用的專案引發的型別宣告更改可能會影響該專案的 ABI。
對採用的影響
在採用嚴格併發檢查的專案中,可能需要修改某些全域性變數型別。
考慮的替代方案
為了隔離,我們可以隱式鎖定變數的訪問,而不需要全域性參與者。 在提供記憶體安全的同時,這可能會給執行緒安全帶來問題,因為開發人員可以輕鬆編寫 non-atomic 的模式:
// value of global may concurrently change between
// the read for the multiplication expression
// and the write for the assignment
global = global * 2
雖然如果我們需要在舊語言模式中做一些源相容的事情,我們可以考慮隱式鎖定,但通常我們的方法只是說舊語言模式是併發不安全的。
它也不適用於非可傳送型別,除非我們強制該值在訪問它時保持隔離。我們可能可以透過提議的跨隔離域安全傳送不可傳送值功能來實現這一目標,但這可能是一個過於先進的功能,無法作為此類基本問題的解決方案來推動。
我們可以將所有需要隔離的全域性變數預設為 @MainActor
。 可以說,讓開發人員考慮選擇會更好(例如,也許它應該只是一個 let 常量)。
訪問控制在理論上是有用的:例如,我們可以知道全域性變數是併發安全的,因為它是檔案私有的,並且該檔案中的所有訪問都來自單個全域性參與者上下文,或者因為它永遠不會 變異了。
不過,這比我們通常希望在編譯器中進行的分析更加全域性化; 我們必須檢查上下文中的所有內容,然後開發人員可能很難理解它為什麼起作用。
未來發展方向
我們不一定需要明確地要求隔離全球參與者; 有空間推斷正確的全球行動者。 全域性角色約束型別的全域性可變變數可以被推斷為約束到該全域性角色(儘管如果變數是不可變的,則沒有必要,因為全域性角色約束類型別是可傳送的)。
4) 討論用 globalActor 標記的方法修改類中的屬性
protocol Po {
@MainActor
func foo()
}
class Base: Po {
var str = ""
func foo() {
str = "w"
print("foo: \(Thread.isMainThread)") // true
}
}
if #available(macOS 10.15, *) {
Task {
let b = Base()
await b.foo()
}
}
RunLoop.main.run()
while true {}
我不明白為什麼我可以直接在標有MainActor的方法中修改屬性?
因為這對我來說似乎是錯誤的。
我相信 Base 及其屬性不在 MainActor 上執行。
回答
您在頂層建立 Task,這隱式地使其在 main actor 上執行。 由於 Base 只是一個類(而不是 actor),因此它的方法在其呼叫者所在的任何上下文中執行,在本例中這是 main actor。
屬性和方法可以單獨與特定參與者相關聯,包括作為協議要求的一部分。 在這種情況下 foo 隱式是 @MainActor,因為 Po 協議如此宣告它。
這可能有點太神奇了 - foo 也是隱式非同步的,儘管它從未真正被標記為非同步,即使在原始協議宣告中也是如此。
為了進一步測試這一點,如果您新增到 Base 例如:
func bar() {
foo()
}
將收到編譯器錯誤 Call to main actor-isolated instance method 'foo()' in a synchronous nonisolated context
,表明編譯器將 foo 視為 @MainActor(但 Base 的其餘部分不是)。
5) 討論在 "super.init" 呼叫之前使用的 "self" 與 "在 super.init 呼叫時未初始化屬性" 衝突
我需要在 init 中建立一個捕獲 self 的閉包來初始化屬性,但我無法使用 self,因為 super.init 尚未被呼叫。 但是,我無法呼叫 super.init,因為該屬性尚未初始化!
import Foundation
class Base {
}
class Sub: Base {
let timer: Timer
var value = 0
// option 1 - try to initialize the property
override init() {
// ERROR: 'self' used before 'super.init' call
timer = .scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
self!.value += 1
}
super.init()
}
// option 2 - try to initialize super first
override init() {
super.init() // ERROR: Property 'self.timer' not initialized at super.init call
timer = .scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
self!.value += 1
}
}
}
除了使屬性既可選又可變(在 super.init 期間初始化為 nil,然後在之後更改它)之外,還有什麼辦法可以解決這個問題嗎?
我有點明白為什麼編譯器不能接受這種情況,但是必須使屬性可選且可變,這很煩人,而一旦類完全初始化,它實際上既不應該為零,也不應該變。
回答
需要在函式內有一個本地變數:
init() {
var futureSelf: Sub? = nil
timer = .scheduledTimer(withTimeInterval: 1, repeats: true) { [weak futureSelf] _ in
futureSelf?.value += 1
}
super.init()
futureSelf = self
}
值得注意的是,編譯器無法知道採用閉包捕獲 self 的物件是否不會立即被呼叫,並且它試圖避免使用半初始化的 self 例項呼叫閉包。
6) 討論顯式使用引用型別後是否應該呼叫 deinit?
我想透過使用 _ = Consumer
物件顯式結束演員/類的生命週期,以避免引入具有單獨作用域的另一級巢狀。 但是,在顯式消費之後不會呼叫該物件的 deinit。 相反,它是在作用域末尾呼叫的。 這是預期行為還是編譯器錯誤? 對於不可複製的結構,它可以按預期工作。
class Object {
deinit { print("deinit object") }
}
struct Noncopyable: ~Copyable {
deinit { print("deinit noncopyable") }
}
func testDeinitAfterConsume() {
do {
let object = Object()
print("before consume")
_ = consume object
print("after consume")
}
print()
do {
let noncopyable = Noncopyable()
print("before consume")
_ = consume noncopyable
print("after consume")
}
}
列印結果:
before consume
after consume
deinit object
before consume
deinit noncopyable
after consume
回答
這是可以理解的。 一般來說,每當物件丟失最後一個引用時,類析構器就會執行,而不考慮變數範圍。
在某種程度上不鼓勵在類去初始化中依賴共享可變狀態,並且強烈不鼓勵依賴與常規程式碼中的副作用相關的順序。 即使沒有最佳化,它通常也會很棘手並且容易出錯。
對物件生命週期的顯式控制是 Swift 中依賴類取消初始化順序的官方方法:
withExtendedLifetime(object) {
// Modify shared mutable state without accessing object.
}
對於區域性變數(包括引數),編譯器(5.7 後)遵循一些保守的生命週期規則,以便大多數“看起來正常”的程式設計模式無需顯式生命週期管理即可工作。 事實上,如果我們按照字面意思理解這個示例,則 deinit 將不會發生,並且我們永遠不會看到以下輸出:
deinit object
before consume
after consume
額外的安全規則是:
如果不安全指標或弱引用可能依賴於區域性變數的生命週期,則編譯器會自動擴充套件該變數持有的任何引用。
如果常規程式碼在 Swift 外部呼叫(包括所有 I/O)或跨任務同步(呼叫非同步函式),則類析構器將不會跨這些邊界重新排序。 這也意味著程式設計師可以透過新增同步程式碼來控制物件的生命週期,而無需 withExtendedLifetime。 在此示例中,呼叫 “print” 被視為同步點,從而阻止最佳化。
這裡有更詳細的描述:https://gist.github.com/atrick/cc03c4d07fb0a7bee92c223ae5e5695b#lexical-variable-scope-can-affect-object-lifetime
在這方面,消耗引數與 “let”、“var” 和非 “消耗” 引數不同,因為它們的生命週期可以在隱式消耗時提前結束:
func bar(_ object: consuming Object) {}
func foo(object: consuming Object) {
bar(object)
print("after consume")
}
總是得到結果:
deinit object
after consume
這為那些關心 ARC 開銷和 CoW 行為的人提供了理想的程式設計模型。 很快,我希望所有區域性變數都具有“消耗”的效果。
推薦博文
掌握 StoreKit2
摘要: 本文介紹了 Swift 中的 StoreKit2,這是一個用於構建應用內購買和訂閱的框架。文章從配置專案和建立 StoreKit 配置檔案開始,介紹瞭如何使用 Store 型別處理應用內購買邏輯。透過示例程式碼和說明,文章展示瞭如何使用 Store 型別來獲取和顯示應用內購買產品列表,並啟動購買流程。還介紹了產品型別和其 purchase 函式,在成功購買時處理交易和驗證過程。文章還涉及了處理掛起狀態和監視交易更新的方法。此外,提到了 StoreKit2 提供的 currentEntitlements 屬性,用於獲取活動訂閱和已購買產品列表。最後,文章給出了一個基本的 App 結構示例,其中包含了 Store 物件,並在應用程式啟動時獲取活動交易。
iOS 防 dump 可行性調研報告
摘要: 文章介紹瞭如何防止 iOS App 被 dump ,包括程式碼混淆、加密、完整性檢查等多層防禦策略,以及伺服器端驗證、動態載入、API 安全性和多因素認證等方案。此外,監控與告警、定期安全審計和安全培訓等後置方案也可以提高 App 的安全性。最後,還介紹了禁止越獄裝置的實施方案,以及 DeviceCheck 和 App Attest API 等新技術方案。
WWDC23 10105 - 打造響應更快的相機體驗
摘要: 文章介紹了 iOS17 提供了一些新的特性,透過延遲圖片處理、快門零延遲、響應捕獲等新特性,以及狀態監聽等措施,能大幅提高相機響應速度,創造更流暢的拍攝體驗。
話題討論
日本核汙水排海,你還會吃海鮮嗎?
- 會
- 不會
歡迎在文末留言參與討論。
關於我們
Swift社群是由 Swift 愛好者共同維護的公益組織,我們在國內以微信公眾號的運營為主,我們會分享以 Swift實戰、SwiftUl、Swift基礎為核心的技術內容,也整理收集優秀的學習資料。
特別感謝 Swift社群 編輯部的每一位編輯,感謝大家的辛苦付出,為 Swift社群 提供優質內容,為 Swift 語言的發展貢獻自己的力量。