# Debug 一直绿,Release 一编就挂:3 个错误,全挤在同一个文件里 > Debug 一直绿,Release 一编就挂:3 个错误,全挤在同一个文件里。类型很典型——写在另一个文件里的 extension 去读 `private` 成员;非隔离上下文调进 - Canonical page: https://obelisk.club/blog/build-log-1307 - Author: Peter Zhang - Published: 2026-10-11 - Tags: build-log Debug 一直绿,Release 一编就挂:3 个错误,全挤在同一个文件里。类型很典型——写在另一个文件里的 extension 去读 `private` 成员;非隔离上下文调进 actor / @MainActor 隔离的代码;还有一个符号自己没 import,靠别的文件的 import 顺带带了过去。这几类错误有一个共同点:只有 whole-module 编译才判得动,而 Debug 构建默认按文件增量编译。 为什么它一直不可见:编译器在增量模式下每次只端详一个文件,访问级别、隔离的跨文件传播、符号到底从哪来——这些「模块级」契约它不查或查不全。于是「日常开发全绿」和「Release 根本编不过」可以长期共存:绿的不是同一个命题。不是缓存,不是偶发,是两种编译模式本来就不承诺同样的检查。 通用的修法,顺序是承重的: 1. 先把出货配置本身变成门。任何回答「能不能打包」的检查,必须真的用 Release / whole-module 编译跑过一遍,而不是拿 Debug 的绿外推。Debug 快是开发便利,不是判决。 2. 错误按类修,不按条数修:`private` 跨文件本来就非法,要么放宽可见性、要么把 extension 挪回声明旁边;隔离违规就显式标注隔离域,让约束长在类型上而不是靠碰巧;用了的符号自己 import,别吃别的文件转手。 3. 最后审一遍 CI:如果整条流水线没有任何一步编译过要发布的配置,那绿灯度量的就不是你要发出去的那个东西。 一分钟自检:现在就在当前树上跑一次 `xcodebuild -configuration Release`(SPM 则 `swift build -c release`),再去 CI 配置里搜「Release」。搜不到,说明你的绿色和你的发布产物之间没有因果关系——今天补上,比提交前一夜补便宜得多。 这个形状和一条老规律同构:通过的检查只证明被检查的部分,不证明要交付的整体。编译器有两种看代码的方式,日常循环只行使了一种,另一种恰恰是出货时用的那种。