环境:Swift 6.3.3 · Xcode 26.5 · iOS Release (-Osize)
相关 Issue:swiftlang/swift#90150


起因:Debug 能跑,Archive 却挂了

最近在给一个 iOS 项目打 Release 包(Archive)时,编译器直接崩溃,报错类似:

Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the crash backtrace.

While running pass ... "EarlyPerfInliner" on SILFunction "@$s...CoordinatorCfD" for 'deinit'

...

isCallerAndCalleeLayoutConstraintsCompatible(swift::FullApplySite)

Command SwiftCompile failed with a nonzero exit code

一开始我以为是内存泄漏或循环引用,毕竟栈里出现了 deinit。但仔细看了栈信息后发现:这不是 App 运行时崩溃,而是 Swift 编译器在优化阶段自己挂了。

  • Debug(-Onone):正常编译、正常运行
  • Release / Archive(-Osize):swift-frontend 在 SIL 优化阶段崩溃

出问题的代码长什么样?

项目里有多处 SwiftUI + UIKit 桥接 的写法,结构很典型:

struct ScrollViewWithOffset<Content: View>: UIViewRepresentable {

@Binding var scrollOffset: CGFloat

var onOffsetChange: (() -> Void)?

var onRefresh: (() -> Void)?

final class Coordinator: NSObject, UIScrollViewDelegate {

var scrollOffset: Binding<CGFloat>

var onOffsetChange: (() -> Void)?

var onRefresh: (() -> Void)?

weak var scrollView: UIScrollView?

var hosting: UIHostingController<Content>?

// ...

}

}

类似模式还出现在:

  • HomeView 的 ScrollViewWithOffset
  • HomeGoldMoreView 的 DisableableScrollView
  • SwiftUIHostController(泛型子类 + 闭包属性)

共同点:

  1. 泛型 UIViewRepresentable 或 UIHostingController 子类
  2. 嵌套 Coordinator: NSObject
  3. 成员混合了 Binding、闭包、UIHostingController<Content>?weak 引用
  4. 没有手写 deinit,依赖编译器自动合成

为什么加 deinit 就能编过?

这是我踩坑时最困惑的一点:手动写 deinit 并不是在修内存泄漏。

对象销毁时,这些属性本来就会被释放。deinit 里写:

deinit {

hosting = nil

scrollView = nil

onChange = nil

onRefresh = nil

}

运行时语义几乎不变,真正改变的是 编译器生成的 SIL(中间表示)。

情况 编译器行为

无手写 deinit

合成复杂释放逻辑 → -Osize 下 EarlyPerfInliner 优化时崩溃

有手写 deinit

SIL 形状更简单 → 优化器绕开 bug → 编译通过

所以这是 Swift 6.3.3 优化器的 workaround,不是 Swift 官方推荐的内存管理方式。

项目里 AtlasMainView 的注释也写得很直白:

手动清空 UI 引用,提前释放资源,减少优化器处理逻辑

当时还不完全理解,现在回头看,说的就是这件事。


我的修复方式

在以下几处加了显式 deinit

1. ScrollViewWithOffset.Coordinator(HomeView)

deinit {

hosting = nil

scrollView = nil

onOffsetChange = nil

onRefresh = nil

}

2. DisableableScrollView.Coordinator(HomeGoldMoreView)

deinit {

hostingVC = nil

}

3. SwiftUIHostController

deinit {

shareCompletion = nil

}

改完后 Release Archive 可以正常打包。


根因:-Osize + 泛型 Coordinator 的合成 deinit

工程里 Debug 用 -Onone,Release 用 -Osize(体积优化):

Debug: SWIFT_OPTIMIZATION_LEVEL = -Onone ✅

Release: SWIFT_OPTIMIZATION_LEVEL = -Osize ❌ 触发崩溃

崩溃发生在编译器的 EarlyPerfInliner 处理泛型 Coordinator 的 deinit(符号后缀 CfD)时。
这是 Internal Compiler Error(ICE),属于工具链 bug,不是业务代码写错。


已向 Swift 官方提交 Issue

我整理了最小复现并提交到 Swift 仓库:

Compiler crash in EarlyPerfInliner when optimizing synthesized deinit of generic UIViewRepresentable.Coordinator (-Osize)

最小复现核心结构:

struct MinimalScrollBridge<Content: View>: UIViewRepresentable {

final class Coordinator: NSObject, UIScrollViewDelegate {

var offset: Binding<CGFloat>

var onChange: (() -> Void)?

weak var scrollView: UIScrollView?

var hosting: UIHostingController<Content>?

// 无 deinit → -Osize 崩溃;有 deinit → 通过

}

}

复现命令:

SDK=$(xcrun --sdk iphoneos --show-sdk-path)

swiftc -Osize -target arm64-apple-ios16.0 -sdk "$SDK" \

-parse-as-library CompilerCrashRepro.swift -o /tmp/out


给其他开发者的建议

如果你也遇到类似情况,可以按下面排查:

1. 先确认是不是编译器崩溃

看日志里有没有:

  • Please submit a bug report
  • EarlyPerfInliner
  • ...CfD for deinit
  • swift-frontend 栈

有的话,优先怀疑 工具链 bug,不要只在内存管理上打转。

2. 临时 workaround

在泛型 Coordinator 或相关类里加显式 deinit,把引用类型成员置 nil

deinit {

hosting = nil

someClosure = nil

}

Binding 是 struct,一般不用清。

3. 其他绕过方式(二选一)

  • Release 优化从 -Osize 改成 -O(可能增大包体)
  • 等 Swift / Xcode 更新修复后再去掉 workaround

4. 帮忙推动修复


小结

误解 事实

不加 deinit 会内存泄漏

主要是 编译器优化 bug

deinit 里 = nil 是规范写法

这里是 改变 SIL、绕过 ICE

Debug 正常就说明代码没问题

Debug 不走 -Osize,路径不同

SwiftUI 与 UIKit 混用时,UIViewRepresentable + UIHostingController 很常见。若 Release 突然编不过、栈里又出现 deinit 和 EarlyPerfInliner,不妨先试试显式 deinit——看起来奇怪,但在 Swift 6.3.3 上确实能救 Archive。


参考链接


如果这篇文章帮到了你,欢迎在 Issue #90150 留言确认是否也能复现,方便 Swift 团队优先修复。

更多推荐