Swift 6.4, 빌드와 디버깅이 모듈 중심으로 바뀐다

이경규·5일 전

Swift 6.4, 빌드와 디버깅이 모듈 중심으로 바뀐다

LLDB부터 dSYM, Bridging Header, Swift Build까지 한 번에 정리

Swift 6.4에서 꽤 큰 변화가 하나 들어옵니다.

새로운 문법이나 SwiftUI API가 아니라 Compiler, Build System, LLDB가 Swift Module을 다루는 방식입니다.

Swift.org가 9월 11일 공개한 내용을 보면 Swift 6.3부터 진행되던 Explicit Module Tracking이 Swift 6.4에서 Debugging까지 본격적으로 연결됩니다.

결과적으로 대부분의 Xcode와 SwiftPM 프로젝트에서는 별다른 코드 변경 없이

  • LLDB의 Module Lookup 안정성 개선
  • 일부 Debugging 지연 감소
  • Bridging Header Import 속도 개선
  • dSYM 크기 감소
  • Build System의 Module 처리 단순화

효과를 받을 수 있습니다.

겉으로는 LLDB 개선처럼 보이지만 조금 더 넓게 보면 최근 Swift Toolchain이 움직이는 방향이 보입니다.

Implicit Dependency
        ↓
Explicit Module
        ↓
Precise Dependency Graph
        ↓
Compiler
Build System
Debugger

각 도구가 Module을 제각각 다시 찾는 구조에서, Build할 때 만들어진 정확한 Module Dependency를 공유하는 구조로 옮겨가고 있습니다.


1. LLDB에서 po가 유난히 느릴 때가 있었던 이유

Breakpoint에서 단순한 Local Variable을 확인하는 작업은 비교적 간단합니다.

p user

이 경우 LLDB는 Debug Info와 Reflection Metadata를 이용해서 변수의 위치와 타입을 알아낼 수 있습니다.

하지만 다음은 조금 다릅니다.

p user.displayName

displayName이 Computed Property라면 실제 Function Call이 필요합니다.

LLDB는 내부 Swift Compiler를 이용해 Expression을 JIT Compile하고 실행합니다.

개념적으로는 이런 작업입니다.

import MyFeature

func lldb_expr(...) {
    user.displayName
}

문제는 여기서 발생합니다.

LLDB가 정확한 MyFeature.swiftmodule을 찾아야 하기 때문입니다.

기존에는 Debug Info가 사실상

Module Name = MyFeature

정도만 가지고 있는 경우가 있었습니다.

그러면 LLDB는 Module 이름을 기준으로 적절한 파일을 다시 찾아야 합니다.

대형 프로젝트에서는 이것이 생각보다 단순하지 않습니다.

MyFeature.swiftmodule

├─ Debug
├─ Release
├─ macOS
├─ Mac Catalyst
├─ Private Module
└─ Public Interface

같은 이름의 서로 다른 Module Variant가 존재할 수 있기 때문입니다.


2. 기존에는 Module 이름을 찾고, 없으면 다시 Compile하기도 했다

과거 LLDB의 Module Import 흐름을 단순화하면 이렇습니다.

Breakpoint

↓

현재 Module 이름 확인

↓

Module Cache 검색

↓

정확한 Module이 없으면

↓

Implicit Module Import

↓

Dependency 재Compile 가능

Cold Module Cache에서는 이 비용이 특히 커질 수 있습니다.

그래서 첫 po나 Expression Evaluation이 유난히 느린 경험이 생기기도 했습니다.

특히

  • 대형 프로젝트
  • Objective-C + Swift 혼합 프로젝트
  • Bridging Header가 큰 프로젝트
  • SDK Module Dependency가 많은 프로젝트

에서 체감될 수 있었습니다.

Swift 6.4가 해결하려는 핵심 문제는 바로 이것입니다.

Module을 이름으로 다시 찾지 말고 Build할 때 사용한 정확한 Module을 기억하자.


3. 시작점은 Explicit Modules다

이 변화는 Swift 6.4에서 갑자기 시작된 것은 아닙니다.

Xcode는 이미 Xcode 16부터 Explicit Module Dependency를 적극적으로 사용하고 있습니다.

기존 방식에서는 Source를 Compile하다 필요한 Module을 Compiler가 발견하면 Module Cache에서 찾거나 그 자리에서 Build할 수 있었습니다.

Source Compile
     ↓
Import 발견
     ↓
Module 필요
     ↓
Module Cache 검색
     ↓
없으면 Compile

Explicit Module 방식에서는 Build System이 먼저 Dependency를 Scan합니다.

Dependency Scan
      ↓
┌───────────────┐
│ Foundation    │
│ MyDomain      │
│ MyNetwork     │
│ FeatureA      │
└───────────────┘
      ↓
Module Build
      ↓
Source Compile

Module Dependency가 Build Graph에 명시적으로 나타납니다.

그러면 Build System은 어떤 Module을 먼저 만들어야 하는지 정확하게 알 수 있습니다.

병렬 Build도 더 효율적으로 Scheduling할 수 있고, Module Build가 실패했을 때 원인도 더 명확하게 확인할 수 있습니다.


4. Swift 6.3부터 Swift Module끼리 Dependency를 직접 기록한다

Explicit Swift Module은 예전부터 Clang Module Dependency를 알고 있었습니다.

하지만 Swift Module끼리의 Dependency Tracking은 충분하지 않았습니다.

Swift 6.3부터 이 부분이 달라졌습니다.

예를 들어

FeatureModule
     ↓
DomainModule
     ↓
NetworkModule
     ↓
Foundation

구조가 있다면 FeatureModule.swiftmodule이 자신이 어떤 Swift Module에 의존했는지 명시적으로 기록할 수 있습니다.

따라서 LLDB가 FeatureModule을 가져오면 그다음 Dependency를 이름으로 다시 찾아다닐 필요가 줄어듭니다.

FeatureModule

→ /path/DomainModule.swiftmodule

→ /path/NetworkModule.swiftmodule

정확한 Module Graph를 따라갈 수 있습니다.


5. -debug-module-path가 중요한 이유

LLDB가 Dependency를 정확하게 알고 있어도 시작점이 필요합니다.

Breakpoint가 걸린 Object File이

어떤 Swift Module에서 만들어진 것인가?

를 알아야 합니다.

Swift 6.3부터 Compiler는 이를 Debug Info에 저장할 수 있습니다.

이때 사용되는 것이

-debug-module-path

입니다.

개념적으로는

Foo.o

→ Foo.swiftmodule의 정확한 위치

를 Debug Info에 기록하는 것입니다.

Xcode나 SwiftPM을 사용한다면 직접 신경 쓸 필요가 없습니다.

Swift Driver가 자동으로 처리합니다.

반면

Bazel
Buck
CMake
Custom Build System

처럼 Swift Frontend를 직접 호출하는 Build System이라면 이 변화가 중요합니다.


6. Swift 6.4에서는 modulewrapadd_ast_path가 사실상 사라진다

기존 Swift Debugging에는 Module을 Binary나 dSYM으로 전달하기 위한 별도의 과정이 있었습니다.

Darwin에서는 Linker에

-add_ast_path

를 넘겼습니다.

Linux나 Windows에서는

-modulewrap

을 사용했습니다.

예를 들어 Linux에서는

Foo.swiftmodule
      ↓
-modulewrap
      ↓
Foo.swiftmodule.o
      ↓
Link

방식으로 Swift Module 자체를 Object File에 집어넣었습니다.

Swift 6.4에서는 Precise Module Tracking 덕분에 이런 과정이 필요 없어집니다.

핵심 변화는 간단합니다.

기존

-modulewrap
-add_ast_path

에서

Swift 6.4

-debug-module-path

중심으로 바뀝니다.

Xcode나 SwiftPM 사용자는 대부분 자동으로 적용됩니다.

하지만 Bazel이나 자체 Build Infrastructure를 운영한다면 Build Rule을 확인할 필요가 있습니다.


7. dSYM에서 Swift Module도 빠진다

이번 변화에서 가장 눈에 띄는 부분 중 하나입니다.

기존 dSYM 안에는 Binary Swift Module이 포함될 수 있었습니다.

MyApp.dSYM

├─ DWARF
├─ Debug Info
└─ Swift Modules

Swift 6.4부터 dsymutil은 Binary Swift Module을 더 이상 dSYM에 포함하지 않습니다.

MyApp.dSYM

├─ DWARF
└─ Debug Info

그 결과 dSYM 크기가 줄어듭니다.

Swift.org에서는 Windows/Linux Binary와 Darwin의 dSYM 모두 크기가 크게 줄어들 수 있다고 설명하고 있습니다.


8. 그런데 Swift Module이 없으면 po는 어떻게 동작할까

여기서 자연스럽게 궁금해집니다.

Swift Module을 dSYM에서 뺐는데
Debugger가 Type을 어떻게 알지?

Swift 5.6 이후 LLDB는 Local Variable의 타입과 Object Description을 확인하는 데 필요한 정보를 Binary의 Reflection Metadata에서 읽을 수 있습니다.

따라서 이런 작업은 계속 가능합니다.

po user

Binary Swift Module이 필요한 것은 조금 더 복잡한 Expression입니다.

예를 들어

p user.computedProperty

또는 Function Call처럼 실제 Compile이 필요한 Expression입니다.

이 경우 LLDB는 Module을 원래 Build 위치에서 찾습니다.

새로운 Precise Module Tracking 덕분에 예전처럼 이름으로 추측하는 것이 아니라 정확한 Module Path를 따라갈 수 있습니다.


9. dSYM에 Binary Module을 보관하지 않는 게 오히려 자연스럽다

Binary Swift Module에는 특징이 하나 있습니다.

정확히 같은 Compiler Toolchain에 종속됩니다.

다른 Swift Compiler Version에서 만들어진 Binary Module을 그대로 읽을 수 있다고 보장할 수 없습니다.

반면 dSYM은 Crash Symbolication 등을 위해 장기간 보관하는 Artifact입니다.

App Release
   ↓
dSYM 보관
   ↓
6개월 후 Crash 분석

이런 용도입니다.

따라서 Toolchain Version에 강하게 종속되는 Binary Swift Module을 장기 보관용 dSYM 안에 넣는 구조 자체가 조금 어색했습니다.

Swift 6.4에서는 이 역할을 분리합니다.

dSYM
→ Long-term Debug Info

Swift Module
→ Build Artifact

Toolchain Architecture 측면에서도 더 깔끔한 구조입니다.


10. Bridging Header도 드디어 빨라진다

오래된 iOS 프로젝트라면 이번 변화에서 가장 체감할 부분일 수 있습니다.

Swift와 Objective-C가 섞여 있는 프로젝트에서는 Bridging Header가 흔합니다.

MyApp-Bridging-Header.h

Swift 6.3까지 LLDB는 Expression Evaluation 과정에서 Bridging Header를 다시 Source부터 Compile할 수 있었습니다.

LLDB

↓

Bridging Header

↓

Header Parsing

↓

Clang Compile

↓

Module Import

Header가 크거나 Objective-C Dependency가 많으면 첫 Debug Expression이 느려질 수 있습니다.

Swift 6.4에서는 Explicit Module 정보를 이용해서 Precompiled Bridging Header와 Dependency를 직접 Import할 수 있습니다.

LLDB
 ↓
Precompiled Bridging Header
 ↓
Explicit Module Dependencies

Fully Modularized Project에 가까운 Debugging 성능을 얻는 것이 목표입니다.

Obj-C와 Swift가 오래 공존한 대형 iOS 프로젝트에서는 상당히 반가운 변화입니다.


11. 결국 Compiler와 Debugger가 같은 Module Graph를 보게 된다

여기까지 정리하면 이번 변화의 핵심이 보입니다.

예전에는 각 단계가 Module을 조금씩 다르게 찾았습니다.

Compiler
   ↓
Module Cache

Build System
   ↓
Dependency Scan

Linker
   ↓
add_ast_path

dSYM
   ↓
Swift AST

LLDB
   ↓
Module Name Lookup

점점 이 구조가 단순해지고 있습니다.

        Explicit Module Graph
                 │
       ┌─────────┼─────────┐
       │         │         │
    Compiler   Build      LLDB
              System

Build할 때 결정된 Module Graph를 Toolchain 전체가 공유하는 방향입니다.

이번 Swift 6.4 변화에서 가장 중요한 부분입니다.


12. Swift가 최근 모듈 중심으로 움직이는 흐름은 이것만이 아니다

여기부터는 Swift 6.4 Debugging 자체와는 조금 다른 이야기지만 같은 방향에서 볼 만한 변화가 있습니다.

Swift 생태계에서는 최근 여러 기능을 보다 명확한 Module과 Toolchain Layer로 정리하는 작업이 계속되고 있습니다.

대표적인 것이 FilePath입니다.


13. FilePath가 swift-system에서 Swift Standard Library로 올라온다

그동안 Swift에서 File System Path를 제대로 표현하려면 swift-systemFilePath를 사용하는 방법이 있었습니다.

import SystemPackage

let path: FilePath

문제는 FilePath가 외부 Package에 있다는 것입니다.

Foundation이나 Swift Runtime처럼 더 낮은 계층에서는 외부 Package를 쉽게 Dependency로 사용할 수 없습니다.

SE-0529에서는 FilePath를 Swift Standard Library의 Swift Module로 이동하는 방향이 승인됐습니다.

앞으로는 개념적으로

let path: FilePath = "/Users/dev/project"

처럼 Swift의 기본 Currency Type으로 사용할 수 있게 됩니다.

구조도

swift-system

FilePath

에서

Swift Standard Library

Swift.FilePath

로 이동합니다.


14. 왜 FilePath를 Swift Module에 넣는 게 중요할까

String이 Path를 표현하는 데 항상 좋은 타입은 아닙니다.

let path: String

은 이것이

  • URL인지
  • 파일 경로인지
  • 일반 Text인지

타입만 보고 구분할 수 없습니다.

FilePath는 File System Path 자체를 타입으로 표현합니다.

더 중요한 이유는 Dependency Layer입니다.

Swift Runtime
Foundation
Swift Testing
Build Tools

같은 Toolchain 구성요소가 공통으로 File Path를 다뤄야 합니다.

FilePath가 외부 Package에 있으면 이런 Core Library가 사용하기 어렵습니다.

Standard Library로 올라오면 공통 Currency Type으로 사용할 수 있습니다.


15. Foundation도 이미 하나의 거대한 Framework에서 나뉘고 있다

Foundation도 같은 맥락에서 볼 만합니다.

Swift 6에서는 주요 Foundation API의 Swift 구현이 여러 플랫폼에서 통합됐습니다.

그리고 전체 Foundation이 필요하지 않은 경우를 위해

import FoundationEssentials

도 사용할 수 있습니다.

FoundationEssentials는 Internationalization과 Localization Data 같은 무거운 부분을 제외하고 핵심 Foundation 기능을 제공합니다.

개념적으로는

Foundation

모든 기능

하나만 사용하는 구조에서

Foundation

├─ FoundationEssentials
├─ Internationalization
└─ 추가 Foundation 기능

처럼 필요한 Layer를 더 명확하게 가져갈 수 있는 방향입니다.

특히 Server Swift나 Embedded 환경에서는 Binary Size와 Dependency가 중요한 만큼 의미가 큽니다.


16. SwiftPM도 Swift Build로 통합되는 중이다

Build System 쪽에서도 비슷한 변화가 진행되고 있습니다.

Swift 6.3에서는 Swift Package Manager에서 Swift Build Integration Preview를 사용할 수 있게 됐습니다.

원래 SwiftPM과 Xcode는 완전히 같은 Build Engine을 사용하지 않았습니다.

Swift 프로젝트가 커질수록

Xcode Build

SwiftPM Build

CI Build

환경 차이가 문제가 될 수 있습니다.

Swift 프로젝트는 현재 Swift Build를 공통 Build Infrastructure로 가져오는 방향으로 움직이고 있습니다.

개념적으로는

Xcode
   ↓
Swift Build

그리고

SwiftPM
   ↓
Swift Build

구조에 가까워집니다.

Swift main branch 역시 Swift Build를 기본 Build System으로 사용하는 방향으로 전환됐습니다.


17. 결국 하나의 Dependency Graph로 수렴하고 있다

이 변화들을 하나씩 보면 서로 다른 기능처럼 보입니다.

Explicit Modules

LLDB Module Tracking

Smaller dSYM

Bridging Header 개선

Swift Build

FilePath

FoundationEssentials

하지만 Toolchain 관점에서는 공통된 방향이 있습니다.

과거에는

Compiler

Build System

Package Manager

Debugger

Core Libraries

각 영역이 어느 정도 독립적으로 Dependency를 관리했습니다.

앞으로는

        Module / Dependency Graph
                  │
     ┌────────────┼────────────┐
     │            │            │
 Compiler     Build System    LLDB
     │            │            │
     └────────────┼────────────┘
                  │
               SwiftPM

처럼 명시적인 Dependency를 공유하는 방향으로 움직이고 있습니다.

Swift 6.4의 LLDB 변화가 중요한 이유도 여기에 있습니다.

단순한 Debugger 최적화가 아니라 Toolchain 전체가 Explicit Module Graph를 신뢰하기 시작했다는 신호이기 때문입니다.


18. 일반적인 Xcode 프로젝트에서는 무엇을 해야 할까

대부분은 아무것도 하지 않아도 됩니다.

Xcode와 SwiftPM을 사용하고 있다면 Swift Driver와 Build System이 대부분 자동으로 처리합니다.

오히려 확인해볼 부분은 오래된 프로젝트입니다.

Objective-C + Swift 혼합

대형 Bridging Header

Custom Build Script

Static Library

Bazel / Buck / CMake

오래된 Module Setting

같은 환경입니다.

특히 자체 Build System을 운영한다면

-modulewrap

-add_ast_path

를 사용하는 Build Rule이 있는지 확인할 필요가 있습니다.

Swift 6.4에서는 이런 처리를 제거하고 -debug-module-path 방식으로 이동할 수 있습니다.


19. iOS 프로젝트에서 확인해볼 것

Swift 6.4 전환을 준비한다면 다음 정도는 확인할 만합니다.

□ Bridging Header가 지나치게 커져 있지 않은가?

□ Objective-C Header가 Module화되지 않은 채 남아 있지 않은가?

□ Static Library Debugging에서 별도 Swift Module 처리를 하고 있는가?

□ Bazel/Buck/CMake Build Rule에 -modulewrap이 남아 있는가?

□ Linker Option에 -add_ast_path를 직접 추가하고 있는가?

□ CI에서 dSYM 크기나 Upload 시간이 큰 편인가?

□ 첫 LLDB Expression Evaluation이 유난히 느린 프로젝트인가?

□ Explicit Modules가 비활성화된 오래된 Target이 있는가?

일반적인 앱에서는 코드 Migration보다 Build Infrastructure 점검에 가까운 변화입니다.


20. Swift 6.4의 변화가 중요한 이유

Swift 개발에서 Module은 예전부터 존재했습니다.

새로운 개념은 아닙니다.

달라지는 것은 Module이 Toolchain에서 갖는 역할입니다.

예전에는

import Foundation

처럼 Source Code의 Namespace와 Dependency 정도로 생각하기 쉬웠습니다.

지금은

Compilation Unit

Dependency Graph

Build Scheduling

Caching

Debugging

Symbol Resolution

까지 Module 정보가 관여합니다.

즉 Module은 단순한 코드 분리 단위가 아니라 Swift Toolchain을 연결하는 공통 단위가 되고 있습니다.


마치며

Swift 6.4에서 가장 눈에 띄는 변화 중 하나는 Debugger가 Module을 찾는 방식입니다.

예전에는 LLDB가 Module 이름을 보고 적절한 Module을 찾아야 했습니다.

이제는 Build할 때 사용한 정확한 Module Path와 Dependency Graph를 따라갈 수 있습니다.

그 결과

LLDB Import 안정성 ↑

Bridging Header Import 속도 ↑

dSYM 크기 ↓

불필요한 Module Compile ↓

Custom Build 단계 ↓

같은 변화가 가능해집니다.

그리고 이 흐름을 조금 넓게 보면 최근 Swift Toolchain의 방향도 보입니다.

Implicit
    ↓
Explicit

Name Lookup
    ↓
Precise Path

Hidden Dependency
    ↓
Dependency Graph

Tool별 Build 정보
    ↓
공유되는 Module 정보

FilePath가 Standard Library로 이동하고, Foundation이 세분화되고, SwiftPM이 Swift Build를 받아들이는 변화까지 같은 시기에 진행되고 있습니다.

모두 똑같은 프로젝트는 아니지만 방향은 비슷합니다.

Swift의 Compiler, Build System, Package Manager, Debugger가 명확한 Module과 Dependency를 중심으로 정리되고 있습니다.

Swift 6.4의 Module Tracking은 그중에서도 일반 iOS 개발자가 직접 체감할 가능성이 높은 변화입니다.

새로운 문법 하나보다 눈에 덜 띌 수 있지만 대형 프로젝트에서는 Build와 Debugging 경험을 꽤 크게 바꿀 수 있는 변화입니다.


참고자료

  • Swift.org — Module Tracking in Swift Debug Info
    Swift 6.3부터 시작된 Precise Module Tracking과 Swift 6.4의 modulewrap, add_ast_path, dSYM, Bridging Header 변화를 설명한 공식 자료입니다.
    Swift.org 공식 글 보기

  • Apple Developer — Building your project with explicit module dependencies
    Xcode의 Explicit Module Dependency Scan과 Module Build Scheduling 구조를 확인할 수 있습니다.
    Apple Explicit Module 공식 문서

  • Swift Evolution — SE-0529 Add FilePath to the Standard Library
    swift-systemFilePathSwift Module의 기본 타입으로 이동하는 배경과 Migration 방향을 확인할 수 있습니다.
    SE-0529 공식 Proposal

  • Swift.org — What's new in Swift: March 2026
    Swift 6.3의 SwiftPM + Swift Build Integration과 Build Infrastructure 통합 방향을 확인할 수 있습니다.
    Swift Build 공식 업데이트

  • Swift.org — Swift Core Libraries
    Swift Foundation의 통합 구현과 FoundationEssentials의 역할을 확인할 수 있습니다.
    Swift Core Libraries 공식 문서

Swift 공식 자료 기준으로 보면, Swift 6.4의 핵심 변화는 LLDB가 Swift Module을 이름으로 추측해서 찾는 대신 Build 시점에 기록된 정확한 Module Path와 Explicit Dependency를 사용할 수 있게 되는 것입니다. 이에 따라 -modulewrap-add_ast_path를 제거할 수 있고, Binary Swift Module을 dSYM에 포함하지 않아도 되면서 Debug Artifact 크기도 줄어듭니다.

또 Swift 6.4에서는 LLDB가 Bridging Header를 Source부터 다시 Compile하는 대신 Precompiled Bridging Header와 Explicit Module Dependency를 직접 사용할 수 있게 됩니다. 일반적인 Xcode/SwiftPM 프로젝트는 별도 대응이 필요하지 않지만 Bazel·Buck·CMake 등 Custom Build System은 -debug-module-path 처리 여부를 확인할 필요가 있습니다.

현실적으로 이번 변화를 “새로운 Module 기능”이라기보다 Compiler → Build System → LLDB가 하나의 Explicit Module Graph를 공유하도록 정리되는 변화로 보는 것이 가장 정확합니다.

profile
iOS 앱 개발자

0개의 댓글