這可能是你聽過最大的誤解:Xcode Build Settings 不是「哪邊看到就改哪邊」,它是有層級、有優先序的合併系統。用 Levels 看的時候,設定值從右往左解析,Target Settings 壓過 Project Settings,最後在 Resolved 變成編譯器真的會吃的那一份。
- 看診斷:Build Settings 用 Levels,不要只看 Combined
- 記方向:解析方向右 → 左,Resolved 在最左邊
- 抓兇手:看哪一欄被 highlight,誰亮誰負責
- 不要硬蓋:要加值用
$(inherited),不然你是在清空 - 減少噪音:共用值丟到 .xcconfig,少在 UI 點來點去
你以為你在改設定 其實你在改優先權
Xcode Build Settings 的 Levels 視圖把設定拆成多欄,代表不同來源。Resolved(最終值)是 build system 實際使用的結果,highlight 的那格就是當下勝出的來源,不是「看起來順眼」的那個欄位。
很多人卡住是因為只盯著 Combined。Combined 很像「報表」。漂亮。也很容易讓你誤判。
Levels 才是現場監視器。你要找誰把值蓋掉,或誰忘了繼承,這邊一眼就看出來。
講到這個我就想到一種很常見的災難:你以為改了 Target 的某個值,CI 卻還是吃另一個值。然後你開始懷疑人生,懷疑 Xcode,懷疑 Apple。
其實只是你改的那欄根本沒贏。
就這樣。
Filters 跟 Display 不是裝飾 你選錯就會看錯
Xcode Build Settings 有兩種控制:Filter 決定你看到哪些設定,Display Mode 決定你看到的是結果還是來源。Basic 只顯示常改的項目,Customized 只顯示你有改過的項目,All 會把預設值與繼承值全部攤開。
Filters 這邊真的會害人。
你開 Basic,看不到某些關鍵設定。你以為不存在。它其實在 All 裡面安靜躺著,還贏了。
Display Mode 更是。
- Combined:只看最後的 Resolved 值。快。也最容易被騙。
- Levels:把每一層來源列出來。慢一點。省掉你半天亂猜。
如果你現在是在追 build error 或 signing 亂跳,真的拜託先切 Levels。
手不要抖。
Levels 欄位拆解 右邊越新越兇
Levels 的每一欄代表一個來源層級,越靠右優先權越高。iOS Default 是 Xcode 的平台預設,.xcconfig 是文字設定檔,Project Settings 是專案層級 UI 值,Target Settings 是目標層級 UI 值,最後由 Resolved 顯示編譯實際使用值。
欄位大致就這幾種:
- iOS Default:Xcode 給的平台預設。你沒碰它它也會存在。
- Project Config file:專案的
.xcconfig。文字檔。好追 diff。 - Project Settings:專案層級的 UI 設定。最容易被人手滑。
- Target Config File:特定 target 的
.xcconfig。拆多 target 時會用到。 - Target Settings:target 層級 UI 設定。優先權很高。也很容易把東西蓋死。
- Resolved:左邊那個最後答案。編譯器認的那個。
原文那個 iOS Deployment Target 的例子其實很好用。
iOS Default 是 iOS 26。Project 的 config file 把它改成 iOS 15。Target 又把它改成 iOS 17。Resolved 就會是 iOS 17。
這種 override 在多 target 很常見。
像 Live Activities 這種 extension,最低系統版本要求常常比主 App 高。你不分開設就會撞牆。
highlight 不是漂亮配色 它是在指認犯人
Levels 裡的 highlight 代表該設定目前被採用的來源,也就是 Resolved 追溯回去的那一格。刪掉某一層的 override,highlight 會往下一個可用來源移動,讓你確認「誰在接手」。
這功能超適合拿來做「刪除實驗」。
你懷疑 target 那格在亂蓋?把那一行在 target 裡 Delete 掉。看 highlight 有沒有跳去 Project Config file 或 iOS Default。
跳了。那就對了。
沒跳。你就知道還有別的地方在蓋你。
順便講個小雷。
有些設定你以為只在 UI 控,實際上你在 build arguments、環境變數也能蓋過去。CI 常見。local 不一定。
這種問題不會在你「盯著 Combined」時自己浮出水面。
$(inherited) 是保命符 不用就等著被你自己害死
$(inherited) 是 Xcode 的繼承關鍵字,用來把目前層級解析到的值保留下來,再加上你在這一層新增的內容。Other Swift Flags 這類會累加的設定,目標層級要加旗標時放 $(inherited),避免把專案層級的旗標整包覆蓋掉。
Other Swift Flags 這種欄位,真的一堆人寫成「我只想加一個 -Dxxx」。
然後直接把原本的 flags 全清空。
編譯當然怪。
正確做法就很土。很直白。
$(inherited) -DNEW_FLAG
你要加值。不是要換血。
講到變數,我腦中會自動跳出 $(CONFIGURATION) 跟 $(SRCROOT)。這兩個在組路徑、分 Debug/Release 的時候很常用。
不過這種東西一旦你開始玩,很容易把設定玩成密室逃脫。
自己看得懂就好。真的。
台灣通路避雷指南 你在 UI 點設定是在自找麻煩
在台灣常見的團隊協作場景,Build Settings 最容易炸的點不是技術,是通路與流程:專案在不同人手上、不同 Mac、不同 CI 平台跑,UI 設定一多就開始出現「我這台可以你那台不行」。把共用設定集中到 .xcconfig,Git diff 乾淨,Review 也比較像人類能看的東西。
這段講白了就是「內行人怎麼避雷」。
- 連鎖賣場式開發:多人一起點 Xcode UI。設定散在 Project/Target。最後每次改 Signing 都像拆炸彈。
- 藥局式開發:只買你需要的。共用的值放 shared
.xcconfig。target 只留差異。乾淨。 - 網購平台式開發:CI/CD 一路跑。環境變數或 build arguments 會蓋設定。你沒把來源列清楚,就會被「同一份 code 不同結果」搞到心態崩。
價位區間我用一個很粗的說法。
小團隊或接案,通常沒人想花「一兩天」整理設定。因為看不到立即產出。
中型以上,出一次大包就會開始覺得:整理設定其實比追 bug 便宜。
提示:真的要做「集中化」,把共用值放在最右邊那個你打算長期維護的層級,通常是 shared .xcconfig。不要每層都留一份一樣的值。那不是備援。那是噪音。
你會在版本控制看到一堆無意義的 diff。
你會在 code review 看到一堆無意義的討論。
然後你會開始討厭改設定。
FAQ 直答區
規則:每題只給能直接用在排查的答案,不講故事。
Levels 跟 Combined 我該看哪個
追問題用 Levels,確認最後值用 Combined;Levels 能看出設定來源與 override 鏈,Combined 只顯示 Resolved 結果。
highlight 代表什麼
highlight 代表該層級的值目前贏了,會被帶到 Resolved 成為實際編譯使用值;刪掉那層 override,highlight 會移到下一個來源。
什麼時候一定要用 $(inherited)
會累加的設定就用,例如 Other Swift Flags、Header Search Paths;你在 target 想加一段值時放 $(inherited),避免把 project 或 xcconfig 先前的值整包覆蓋。
為什麼我刪了 target 的設定 還是沒變
因為同一個 key 可能在 Project Settings、.xcconfig、或 build arguments/環境變數仍有 override;切到 Levels 找 highlight 的那欄就能定位來源。
xcconfig 到底值不值得導入
多 target、多人協作、CI 會吃環境變數的專案就值得;.xcconfig 把共用設定文字化,diff 好看、可重用、也比較不會被 UI 手滑污染。
結論金句:Xcode Build Settings 不是「設定清單」,是「優先序系統」,你把共用值集中到 .xcconfig,就會少掉一堆人為噪音。
好,最後問你一個很實際的。
你現在的專案是「每個 target 都一堆重複設定」那種,還是「shared xcconfig 一份控住大局」那種?你最常被哪個設定坑,Signing、Deployment Target,還是 flags?
