← 所有文章

@State 巨集:Xcode 27 開始拒絕編譯的兩種寫法

Apple 的 iOS 27 發行說明在 @State 這一條目的開頭,先講了一個 SwiftUI 從 iOS 13 起就一路帶著的錯誤:3「以運算式作為初始值的 @State,過去每次 view struct 重新實體化時都會把運算式重新求值一次。以 @State private var model = Model() 為例,這代表 Model.init() 在該 view 的生命週期中會被呼叫很多次。」1

修正方式是重寫。「Xcode 27 引入全新的 @State 實作,避免這種重複求值。新行為可回溯部署到 iOS 17 及對應版本的作業系統。新的 @State 以 Swift 巨集實作,除少數例外之外,與 property wrapper 版本大致原始碼相容。」1

請留意這句話裡的觸發條件。Apple 點名的是 Xcode,不是部署目標。

重點摘要

  • Xcode 27 把 @State 改以 Swift 巨集實作,而 Apple 的符號說明頁從兩個方向都寫明了這次切換:State 結構頁寫著「當您使用 Xcode 27 或更新版本建置時,系統會改用 State() 巨集」,State() 巨集頁則寫著「當您使用 Xcode 26 或更舊版本建置時,系統會改用 State property wrapper」。23
  • 部署目標無法讓您豁免這個巨集。它與被取代的 property wrapper 帶著同樣的 iOS 13.0 可用性,切換依據是 Xcode 版本,而非部署目標。至於執行期的那一半,只回溯部署到「iOS 17 及對應版本的作業系統」,因此仍支援 iOS 15 或 16 的專案只會拿到編譯期的變動,拿不到執行期的修正。
  • 靜默丟棄的行為是舊有的,而且沒有改變。Apple 寫道,這個行為「並未因為巨集而改變,但其中有些情況已無法編譯」。1 巨集把一個會吃掉初始化器賦值的錯誤,變成了建置失敗。
  • 編譯失敗集中在兩種寫法:初始化器對一個同時在宣告處帶有初始值的 @State 屬性賦值;以及在 extension 中呼叫編譯器為全私有 struct 合成的 memberwise 初始化器。1 Apple 寫的是「有些情況」,不是全部,而且從未列出究竟是哪些。
  • 動筆之前,我先稽核了四個已上架的 App:267 個 @State 宣告,其中 201 個在宣告處帶有初始值,兩種會壞掉的寫法一個都沒有,只有一次差一行的近似案例,另外還逮到一個在 macOS 上會製造假清白結果的 grep 慣用寫法。6

這一條寫在 iOS 與 iPadOS 27 發行說明的 SwiftUI 章節,而不是 Xcode 27 發行說明裡;後者根本沒有對應條目。7 一個被 Apple 歸因於 Xcode 的變動落在這裡,位置實在奇怪,也難怪這則消息會晚一步才傳到大家耳裡。

Apple 修掉的那個效能錯誤

以發行說明的標準來看,Apple 對舊行為的描述直白得罕見:Model.init()「在該 view 的生命週期中會被呼叫很多次」。1 SwiftUI 不斷重新實體化 view struct,而每一次重新實體化都會把等號右邊的運算式重新求值一遍。既有狀態優先,所以 SwiftUI 會把結果丟掉——但那些工作照樣做了一遍。

巨集說明頁用一句話寫下新的約定:「State() 屬性只在 SwiftUI 第一次實體化該 view 時建立它的預設值。」2

Apple 的 SwiftUI 更新頁補上了一個發行說明沒提的限定條件,而這個限定條件才是真正值得據以規劃的部分:「請在 Xcode 27 或更新版本中建置專案,@State 屬性才會使用 State() 巨集,在 AppSceneView 中建立狀態值。這項變更只有在屬性為 class 時,才會只初始化並儲存一次。」4

「屬性為 class 時」把收益範圍收得相當窄,卻正好指向現代 SwiftUI 程式碼裡到處都是的寫法。把 @Observable 物件存進 @State 是 Apple 有文件記載的做法,巨集說明頁自己的範例就是這樣持有一個 @Observable class Library2 struct 的初始化器通常很便宜;但一個會開啟儲存區、啟動查詢或註冊觀察者的 class 初始化器並不便宜,而 Apple 說它會跑「很多次」。1

真正改變的是什麼,沒變的又是什麼

Apple 用同一句話交代語意與編譯行為,而這句話的前後兩半指向相反的方向:「若您在 @State 宣告處提供初始值,又試圖在初始化器中對它賦值,初始化器提供的值會被丟棄。這個行為並未因為巨集而改變,但其中有些情況已無法編譯。」1

程式碼的意義毫無變化。在 property wrapper 之下,對一個在宣告處已有初始值的 @State 屬性賦值的初始化器,什麼事都不會做,而且悄無聲息地通過編譯。巨集沒有動這條規則,只是把那份沉默拿掉了。

被淘汰的,是代價最高的那種失敗模式:開發者寫了一個初始化器,把標題一路傳進去,卻看到畫面上出現錯誤的標題,於是跑進 view body 裡東翻西找。編譯器一直都知道答案,卻沒有辦法說出口。

Apple 自己的範例裡有兩行註解,把重點說得很清楚:

struct StickerPageView: View {
    @State private var page = StickerPage()
    let title: String

    init(title: String) {
        // `title` won't have any effect
        // this also won't compile with @State macro
        self.page = StickerPage(title: title)
        self.title = title
    }
}

「不會有任何效果」與「無法編譯」寫在相鄰的兩行。前者描述的是 Xcode 26,後者描述的是 Xcode 27。同一段程式碼,同樣的語意,判決不同。

修法是移除宣告處的初始值:

struct StickerPageView: View {
    @State private var page: StickerPage // no initial value expression
    let title: String

    init(title: String) {
        self.page = StickerPage(title: title) // works!
        self.title = title
    }
}

Apple 把規則收斂成一句指示:「若要透過初始化器賦予初始值,就不要在 @State 宣告處提供初始值。」1

因此準確的結論不是巨集弄壞了初始化器賦值。初始化器賦值本來就是壞的,巨集只是第一個把這件事說出口的東西。把這次變動說成退步的人,方向弄反了——不過對一條發行分支而言,實際後果一模一樣:昨天過得去的建置,今天過不去。

有一點保留值得帶著走。Apple 寫的是「有些情況」,不是全部,而且從未列出是哪些。在 Xcode 27 下順利建置,是關於您程式碼的證據,而不是關於這條規則的證明。

合成的初始化器消失了

第二個例外與初始值無關,而且它抓到的程式碼在呼叫端根本沒提到 @State

「當一個 struct 的所有儲存成員都是 private 時,編譯器會合成一個 private init,可在同型別的 extension 中使用:」1

struct StickerPageView: View {
    @State private var page: StickerPage
    private let title: String
    ...
}

extension StickerPageView {
    init(title: String, _ page: StickerPage) {
        self.init(page: page, title: title) // using the synthesized init
    }
}

「state 巨集會停用這個合成的初始化器,因此上面的程式碼不再能夠編譯。緩解方式是明確地為成員賦值:」1

extension StickerPageView {
    init(title: String, _ page: StickerPage) {
        self.title = title
        self.page = page
    }
}

這種寫法比第一種更難找,因為出問題的呼叫端和造成問題的 @State 位在不同的宣告裡。拿 @State 去 grep 是撈不出來的。

Apple 只說了結果,就此打住。而巨集公開的宣告與這個症狀是對得上的:

@attached(accessor, names: named(init), named(get), named(set))
@attached(peer, names: prefixed(`_`), prefixed(`__`), prefixed(`$`))
macro State()

一個提供 getset 的 accessor 巨集,會改變編譯器眼中這個屬性的本質,而 memberwise 初始化器的合成正是以儲存屬性為依據。2 把這份宣告讀成原因,是我的推論,不是 Apple 的說法;而緩解方式並不取決於機制:把賦值一行一行手寫出來就是了。

泛型推論與 property wrapper 組合

剩下的兩個例外,Apple 各給了一句話。雖然兩者都不會波及太多專案,但都值得在遷移檢查清單上留一行。

泛型推論的處理比這條目裡其他任何部分都更模糊:「在少數情況下,巨集實作對 @State 泛型引數的自動推論會比較不靈活。請把型別寫得更明確。」1 Apple 沒點名任何案例,也沒給出可供搜尋的診斷訊息。緩解方式就是在宣告處明確標註型別,編譯器抱怨到哪裡就補到哪裡。

至於組合,Apple 把它說成一項限制,而不是一次破壞:「不支援將 @State 與其他 property wrapper 或巨集組合使用。」1 如果您曾經把 @State 包進自訂的 property wrapper 裡,值得回頭確認一下;也值得記住,Apple 的措辭是「不支援的領域」,而不是「以前可行」。

沒有任何部署目標能豁免

來自三個 Apple 頁面的三句話,把所有退路都堵上了。

State() 巨集這個符號標示的可用性涵蓋 iOS 13.0、iPadOS 13.0、macOS 10.15、tvOS 13.0、watchOS 6.0 與 visionOS 1.0,與它取代的 property wrapper 完全一致,所以把最低版本往下調並不能繞過巨集。23 而切換的依據是編譯器,不是部署目標:「當您使用 Xcode 27 或更新版本建置時,系統會改用 State() 巨集。」3

這次變動在執行期的那一半有個下限,編譯期的那一半沒有。Apple 寫道,新行為「可回溯部署到 iOS 17 及對應版本的作業系統」,這在下方留了一道缺口:部署目標為 iOS 15 或 16 的專案,在編譯期會拿到巨集,連同那些原始碼相容性的例外一起拿到,卻拿不到回溯部署的執行期行為。1 Apple 沒說這些目標拿到的是什麼。如果您支援比 iOS 17 更舊的系統,請把建置失敗當成確定會發生的事,而把重複初始化的修正當成在您最舊的裝置上尚未證實。

用 Xcode 27 開啟專案、建置,您拿到的就是新的 @State。這個決定裡沒有 Info.plist 鍵、沒有建置設定,也沒有任何可用性檢查插得上手。

27 這個週期帶來三項破壞性變動,開發者總把它們歸在同一個標題底下,但三者觸發的時機各不相同。啟動畫面要求綁定的是「以 27.0 SDK 或更新版本建置的 App」,代價是 App Store 退件。場景生命週期強制規定綁定的是「以最新 SDK 建置」的 App,代價是 App 根本啟動不了。@State 巨集綁定的是 Xcode 版本,代價是一次建置失敗。Apple 把前兩項寫成對 SDK 的要求,第三項寫成對工具鏈的要求,這讓 @State 排在最前面:您會在第一次建置時就撞上它,比動到任何 plist 鍵或部署目標都更早。

迫使您換上新工具鏈的壓力,是照公開時程來的,只是 iOS 27 的時程還沒出現。Apple 的要求頁目前寫著:「自 2026 年 4 月 28 日起,上傳至 App Store Connect 的 App 必須使用 Xcode 26 或更新版本,並搭配 iOS 26、iPadOS 26、tvOS 26、visionOS 26 或 watchOS 26 的 SDK 建置。」5 針對 iOS 27 的 SDK,Apple 尚未公布相對應的日期。近幾年每逢春季 Apple 都會把 SDK 的最低版本往上拉,所以預期 27 也會有期限是合理的推測,而不是事實;您在別處看到的任何具體日期,都是推論。

四個已上架的 App 裡實際有什麼

在評論別人的程式碼之前,我先把稽核跑在自己的程式碼上:四個 App Store 上架的 App,全部是 SwiftUI,目前全都以 Xcode 26.6 建置。6

App Swift 檔案數 @State 宣告數 宣告處帶初始值 宣告 @State 的型別數
Reps 77 120 83 31
Return 57 57 42 14
Ace Citizenship 26 63 54 11
Banana List 55 27 22 8
總計 215 267 201 64

兩道命令就能把工作範圍縮小。第一道找出在宣告處帶有初始值的 @State,而 @State 後面那個字元類別是有存在必要的:[^A-Za-z0-9_] 會把 @StateObject 擋在結果之外,單純搜尋 @State 做不到這件事。

grep -rn --include="*.swift" \
  --exclude-dir=.build --exclude-dir=DerivedData --exclude-dir=build \
  -E '@State[^A-Za-z0-9_][^=]*[^!<>=]=[^=]' .

這道命令假設屬性標記與宣告寫在同一行。如果 @State 單獨寫成一行、底下才是 private var page = StickerPage(),就不會有任何比對結果——這其實就是下面要談的那種假清白失敗,只是換了一套外衣。漏掉的部分請讀成「還沒檢查」,而不是「乾淨」。

第二道把範圍縮到同時宣告了初始化器的檔案,那是第一個例外唯一咬得到人的地方:

find . -name "*.swift" -not -path "*/build/*" -not -path "*/DerivedData/*" -print0 |
while IFS= read -r -d '' f; do
  grep -qE '@State[^A-Za-z0-9_][^=]*[^!<>=]=[^=]' "$f" || continue
  grep -qE '^[[:space:]]*(private |public |internal |fileprivate )?init[[:space:]]*[(<]' "$f" || continue
  echo "$f"
done

這個迴圈看起來比直接接 xargs 笨重,但在 macOS 上這份笨重是划得來的。BSD 的 grep 不會像 GNU grep 那樣為 -Z 輸出 NUL 分隔符號,所以 grep -rlZ ... | xargs -0 grep -l 交給第二個 grep 的,是一整團用換行接起來的字串。若專案路徑裡含有空白字元——例如 Banana List——您會在標準錯誤上看到滿螢幕的「No such file or directory」,標準輸出則什麼都沒有,看起來就跟一次乾淨的稽核一模一樣。6 把空輸出讀成「沒有比對結果」,恰恰會在最需要檢查的專案上得到相反的答案。

在 215 個 Swift 檔案裡,篩選後的短名單只有 20 個:Reps 九個、Return 六個、Ace Citizenship 三個、Banana List 兩個。把這 20 個逐一手讀,四個專案得到的結果一致。

  • Apple 描述的第一種寫法,宣告處有初始值,同型別的初始化器裡又對同一個屬性賦值:零個
  • 第二個例外,extension 呼叫某個宣告了 @State 的 struct 的合成 memberwise 初始化器:零個
  • 組合例外,同一個宣告上 @State 與另一個 property wrapper 或巨集並存:零個

有一個差一步的案例值得描述,因為它離 Apple 叫您移除的那種寫法只差一行。Reps 裡一個個人資料設定的 view 宣告了 @State private var healthWriteStatus: HealthAuthorizationState = .notRequested,接著在它的初始化器裡寫下 self._healthWriteStatus = State(initialValue: ...)。兩個地方都帶了值,所以 Apple 那句緩解指示逐字適用:不要在 @State 宣告處提供初始值。1 只不過這裡的賦值用的是底線形式,而不是 Apple 範例裡的包裝值形式,而 Apple 的發行說明從頭到尾沒提過底線形式。

這也就留下了我的稽核無法回答的問題。_x = State(initialValue:) 這個慣用寫法在四個 App 中的三個裡共出現 15 次,而它至今仍是用初始化器參數餵入狀態的標準做法。Apple 的條目沒有處理它。巨集宣告確實會產生一個以底線為前綴的 peer,2 這是線索,不是保證。我用的是 Xcode 26.6(build 17F113),所以無論是這個慣用寫法,還是它會產生什麼編譯錯誤文字,我都無法驗證。6 手上有 Xcode 27 beta 的人,一個下午就能把兩件事都定案。在有人做到之前,請以宣告的形狀去搜尋程式碼,而不是去搜尋錯誤訊息字串。

267 個宣告給出的誠實結論是:大多數 SwiftUI 程式碼會毫髮無傷地過關,這與 Apple「大致原始碼相容」的說法相符。1 有風險的是那些手寫初始化器的 view,而它們的分布相當集中:四個程式碼庫裡,同時擁有手寫初始化器與帶著自身初始值的 @State 的 Swift 檔案,不到十分之一。

常見問題

我需要修改 _page = State(initialValue:) 這類呼叫嗎?

Apple 的條目沒說。底線形式直接對投射出的儲存體賦值,而不是對包裝值賦值,而它在發行說明裡完全沒有出現:沒有 initialValue、沒有底線範例,正反兩面都沒提。1 巨集公開的宣告確實會產生一個以 _ 為前綴的 peer,這是線索,不是保證。2 我稽核的四個 App 裡有三個用了這個寫法,總共 15 次,所以這個答案影響到的程式碼庫,比那兩個有文件記載的例外還多。6 在 Apple 說明之前,請用 Xcode 27 建置專案,讓編譯器回答,不要憑猜測重構。

巨集改變了在初始化器裡賦的值會有什麼結果嗎?

沒有。Apple 說得很明確,丟棄行為「並未因為巨集而改變,但其中有些情況已無法編譯」。1 在 Xcode 26 下,對一個已在宣告處帶有初始值的 @State 屬性賦值的初始化器,什麼都不做,而且編譯通過。在 Xcode 27 下,其中有些情況會建置失敗。語意原地不動,診斷訊息終於出現,所以這裡的建置失敗,是編譯器在報告您本來就有的錯誤。

我要如何在專案裡找出有風險的程式碼?

先搜尋在宣告處帶有初始值的 @State,把結果限縮到同時宣告了初始化器的檔案,然後把這份短名單逐一手讀。用字元類別排除 @StateObject@State[^A-Za-z0-9_]);在 macOS 上請用 find -print0 迴圈,而不是 grep -rlZ | xargs -0,因為 BSD 的 grep 不會輸出 NUL 分隔符號,任何含有空白字元的路徑都會產生看起來像通過的空輸出。6 四個 App、215 個 Swift 檔案,短名單只有 20 個檔案。另外請單獨搜尋在 view struct 上呼叫 self.init(...) 的 extension,因為第二個例外不會在造成它的 @State 附近留下任何痕跡。

我的 App 真的會變快嗎?

只在特定情況下會,而 Apple 給出的範圍比發行說明讀起來更窄。發行說明談的是一般性地避免重複求值,1 但 SwiftUI 更新頁那一條說,這項變更「只有在屬性為 class 時,才會只初始化並儲存一次」。4 收益落在 @State private var model = SomeObservableClass() 這種寫法上:舊實作會在每次 view 重新實體化時執行一遍 class 的初始化器,然後把結果丟掉。像 @State private var isPresented = false 這種,從來就不是問題所在。

重點整理

給 iOS 開發者: - 要稽核的是兩種寫法,不是一種。第一種出現在與初始化器相鄰的 @State 宣告上;第二種出現在 extension 裡,對一個所有儲存成員都是 private 的 view struct 呼叫 self.init(...)1 - 修第一種請刪掉宣告處的初始值,不要刪掉初始化器裡的賦值。Apple 的指示是把初始化器留作唯一來源,讓宣告保持空白。1

給維護較舊 SwiftUI 程式碼的團隊: - 審查的力氣要配在短名單上,而不是整個程式碼庫。在我的四個 App 裡,同時擁有帶初始值的 @State 與手寫初始化器的檔案是 215 個中的 20 個,而第一種寫法的每一個實例都必然落在其中。第二種寫法請另外搜尋,因為它藏在 extension 裡。6 - 把建置失敗當成找到了一個錯誤。編譯器現在拒絕的那個初始化器賦值,SwiftUI 本來就在丟棄它,所以這項變動咬到哪裡,那裡本來就有某個行為是錯的。1

給發布管理者: - 在同一個週期裡,把 @State 稽核排在由 SDK 觸發的工作之前。啟動畫面鍵場景生命週期綁定的都是您用來建置的 SDK;@State 綁定的是您用來建置的 Xcode,所以它會先到。13 - 不要照著某個 iOS 27 SDK 的期限來規劃。Apple 公布的最低要求仍然是 iOS 26 的 SDK,自 2026 年 4 月 28 日起生效,而針對 27,Apple 什麼都還沒宣布。5


三道強制關卡在同一個週期裡一起到位,卻在三個不同的地方讓您跌倒:啟動畫面鍵擋下一次送審,場景強制規定擋下一次啟動,@State 巨集擋下一次建置。至於同一個 SDK 裡還有什麼,請參閱iOS 27 的 SwiftUI 有哪些新變化。完整系列的入口是Apple 生態系統系列

參考資料


  1. Apple,iOS & iPadOS 27 Release Notes,SwiftUI 章節,New Features(radar 105893279)。此為以下各項的來源:舊行為的描述(”A @State declared with an expression as its initial value used to evaluate the expression each time the view struct re-instantiates. In the case of @State private var model = Model(), this means Model.init() gets called many times throughout the view’s lifetime”)、新實作(”Xcode 27 introduces a new @State implementation that avoids this repeated evaluation. This new behavior back-deploys to iOS 17 aligned OSes. The new @State is implemented with a Swift macro. It is largely source compatible with the property wrapper version, with a few exceptions”)、第一個例外及其指示(”If you provide an initial value at @State declaration, and also try to assign a value to it in an initializer, the initializer value is discarded. This behavior has not changed because of the macro, but some such cases no longer compile” 與 “When assigning initial value via an initializer, do not provide an initial value at the @State declaration”)、第一個例外的兩段 StickerPageView 程式碼、第二個例外(”When all stored members of a struct are private, the compiler synthesizes a private init that can be used in an extension of the same type” 與 “The state macro disables this synthesized initializer. So the code above no longer compiles. To mitigate, assign value to members explicitly”)及其兩段程式碼、泛型推論註記(”In rare situations, the automatic inference of generic arguments of @State is less flexible with the macro implementation. Write the type with more specificity”),以及組合註記(”Composing @State with other property wrappers or macros is not supported”)。所有程式碼清單皆逐字轉錄。已於 2026 年 7 月 25 日對照 Apple 的文件 JSON 驗證,因為 HTML 頁面是透過 JavaScript 呈現其內容。 

  2. Apple,State() macro,SwiftUI 巨集參考文件。此為以下內容的來源:公開宣告(@attached(accessor, names: named(init), named(get), named(set))@attached(peer, names: prefixed(_), prefixed(__), prefixed($))macro State())、可用性清單(iOS 13.0、iPadOS 13.0、Mac Catalyst 13.0、macOS 10.15、tvOS 13.0、visionOS 1.0、watchOS 6.0)、關於工具鏈的旁註(”When you build with Xcode 26 or earlier, the system uses the State property wrapper instead”)、新的初始化約定(”A State() property instantiates its default value the first time SwiftUI instantiates the view”),以及「Store observable objects」範例中將 @Observable class Library 存於 @State 的寫法。 

  3. Apple,State,SwiftUI 結構參考文件。仍宣告為 @frozen @propertyWrapper struct State<Value>,可用性自 iOS 13.0、iPadOS 13.0、Mac Catalyst 13.0、macOS 10.15、tvOS 13.0、visionOS 1.0 與 watchOS 6.0 起。此為反方向工具鏈旁註的來源:”When you build with Xcode 27 or later, the system uses the State() macro instead.” 

  4. Apple,SwiftUI Updates,2026 年 6 月,General。此為 class 限定條件的來源:”Build your project in Xcode 27 or later so that the @State attribute uses the State() macro to create a state value in an App, Scene, or View. This change only initializes and stores your property once when it’s a class.” 

  5. Apple,Upcoming requirements,Apple Developer News。此為目前 SDK 最低要求的來源:”Since April 28, 2026 Apps uploaded to App Store Connect must be built with Xcode 26 or later using an SDK for iOS 26, iPadOS 26, tvOS 26, visionOS 26, or watchOS 26.” 已於 2026 年 7 月 25 日查核;該頁面並未列出任何提及 iOS 27 SDK 的要求。 

  6. 作者對四個已上架 SwiftUI App(Reps、Return、Ace Citizenship 與 Banana List)的稽核,環境為 macOS 26.5.2 搭配 Xcode 26.6(build 17F113),時間為 2026 年 7 月 25 日。計數來自一支依大括號深度解析各型別宣告、並在其中界定初始化器主體範圍的腳本,並與上文公布的 grep 命令交叉核對;後者在四個專案中都完全重現了各專案宣告處帶初始值的計數(83、42、54、22)。BSD grep 的行為已直接確認:在 macOS 上,grep -rlZ 輸出的是以換行分隔而非以 NUL 分隔的結果,因此 xargs -0 收到的是單一個接合起來的引數,而只要路徑含有空白字元,整條管線就會以「No such file or directory」失敗。_x = State(initialValue:) 這個慣用寫法的計數(在 Reps、Return 與 Banana List 中共出現 15 次)來自同一次掃描。Xcode 27 下的行為並未測試,此處也未報告任何編譯錯誤文字,因為所用機器上並未安裝 Xcode 27。 

  7. Apple,Xcode 27 Release Notes。已於 2026 年 7 月 25 日搜尋 radar 105893279 以及任何描述 @State 巨集的條目;兩者皆未出現。唯一提到 @State 的地方是一則與此無關的 MusicKit 修正(radar 176947544)。 

相關文章

Xcode 27 移除 ld64,並要求模組名稱唯一

Xcode 27 移除 ld64 連結器,並要求 Clang 模組名稱唯一。兩者都在升級工具鏈時觸發。附七個專案的稽核結果與實測指令。

8 分鐘閱讀

iOS 27 啟動畫面規則:四個鍵擇一,否則退件

以 iOS 27 SDK 建置的 App 必須宣告啟動畫面,否則 App Store 會直接退件。本文說明這四個鍵,以及如何稽核 plist 由建置系統自動產生的 target。

5 分鐘閱讀