← 홈으로 돌아가기
iOS Development Guide

Tuist 학습 Q&A 정리

pbxproj, Workspace, Project, Target, Scheme, Configuration, dSYM, Symbol Table, .swiftmodule, dyld 등 Tuist와 Xcode 프로젝트 구조에 대한 Q&A 정리.

Tuist 4.x 기준 컴파일/링킹 심화 dyld 동작 원리
학습 날짜

기록 없음

1. pbxproj 파일

Q: pbxproj는 XML 파일인가?

아니요. OpenStep plist 형식 (NeXTSTEP 시대 유산)

// .pbxproj 예시 (OpenStep plist 형식) { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { 13B07F961A680F5B00A75B9A /* AppDelegate.swift */ = { isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; }; }; rootObject = 83CBB9F71A601CBA00E9B192; }
형식특징
OpenStep plist=; 사용, NeXTSTEP 시대 유산
XML plist<key>, <string> 태그 사용
Binary plist바이너리 형식, 사람이 읽기 불가

Q: pbxproj가 Git 충돌이 잦은 이유?

1. 객체 ID가 랜덤 해시값

// 개발자 A가 LoginView.swift 추가 A1B2C3D4E5F6G7H8 /* LoginView.swift */ = { ... }; // 개발자 B가 같은 LoginView.swift 추가 9X8Y7Z6W5V4U3T2S /* LoginView.swift */ = { ... }; // → 머지 시 충돌 발생

2. 파일 하나 추가 시 여러 섹션 변경

파일 1개 추가 → 4개 섹션 변경: - PBXFileReference (파일 참조) - PBXBuildFile (빌드 대상) - PBXGroup (폴더 구조) - PBXSourcesBuildPhase (컴파일 순서)

3. Xcode 버전마다 정렬 순서 다름

// Xcode 14 children = (AppDelegate.swift, HomeView.swift, LoginView.swift); // Xcode 15 (아무것도 안 바꿔도) children = (HomeView.swift, LoginView.swift, AppDelegate.swift);

Q: pbxproj는 언제 생겼나?

연도사건
1988NeXT에서 Project Builder IDE 출시
1997Apple이 NeXT 인수
2003Xcode 1.0 출시, pbxproj 형식 유지
현재36년 된 레거시 형식 계속 사용 중

P = Project, B = Builder, X = Xcode (또는 NeXTSTEP의 X)

2. Workspace, Project, Target 관계

Q: project.pbxproj가 "핵심 설정 파일"이란 무슨 의미?

MyApp.xcodeproj/ ← 폴더 (패키지) ├── project.pbxproj ← 핵심 설정 파일 ├── project.xcworkspace/ ← 단일 프로젝트용 워크스페이스 └── xcuserdata/ ← 사용자별 설정
설정예시
파일 목록어떤 .swift 파일들이 포함되는지
폴더 구조Xcode 네비게이터 계층
타겟 정의App, Test, Widget 등
빌드 설정Debug/Release, 컴파일러 플래그
의존성링크할 프레임워크/라이브러리

Q: .xcodeproj vs .xcworkspace 차이?

.xcodeproj.xcworkspace
정체단일 프로젝트여러 프로젝트 묶음
용도앱 하나만 개발앱 + SPM + 여러 프로젝트
외부 모듈 방식뭘 열어야 하나
SPM.xcodeproj OK
CocoaPods.xcworkspace 필수
Tuist.xcworkspace 필수

Q: 여러 프로젝트 = 여러 타겟?

아니요, 다른 개념.

Workspace (.xcworkspace) │ ├── Project A (.xcodeproj) │ ├── Target: App │ ├── Target: Tests │ └── Target: Widget │ └── Project B (.xcodeproj) ├── Target: Framework └── Target: FrameworkTests
개념설명
Target빌드 결과물 1개
Project타겟들의 묶음
Workspace프로젝트들의 묶음

Q: 타겟의 "빌드 결과물이 하나"란?

Target: MyApp → 빌드 → MyApp.app Target: MyTests → 빌드 → MyTests.xctest Target: MyWidget → 빌드 → MyWidget.appex
Target 종류빌드 결과물확장자
App실행 가능한 앱.app
Unit Test테스트 번들.xctest
Widget Extension위젯.appex
Framework프레임워크.framework
Static Library정적 라이브러리.a

Q: 프로젝트 빌드하면 모든 타겟이 빌드되나?

아니요. 프로젝트가 아니라 Scheme을 빌드하는 것.

프로젝트 빌드 (X) → 스킴 빌드 (O) ↓ 스킴이 지정한 타겟만 빌드

3. Scheme (스킴)

Q: Scheme이 뭐야?

"어떤 타겟을, 어떤 설정으로, 어떻게 실행할지" 정의한 것

Scheme: "MyApp-Dev" │ ├── Build → 어떤 타겟을 빌드할지 ├── Run → 어떤 타겟을 실행할지 + Configuration ├── Test → 어떤 테스트 타겟을 돌릴지 ├── Profile → Instruments 프로파일링 설정 ├── Analyze → 정적 분석 설정 └── Archive → 앱스토어 배포용 빌드 설정

Q: 왜 Scheme이 여러 개 필요한가?

같은 타겟, 다른 설정으로 실행하기 위해:

SchemeConfigurationAPI 서버
MyApp-DevDebug_Devdev-api.com
MyApp-QADebug_QAqa-api.com
MyApp-ProdRelease_Prodapi.com

Q: Scheme vs Target 차이?

TargetScheme
정의빌드 결과물 정의빌드 방법 정의
내용소스, 리소스, 의존성타겟 + Configuration + 실행 옵션
결과.app, .framework 등없음 (실행 설정일 뿐)
비유요리 레시피주문서
Target 없이 Scheme? ❌ 불가능 Scheme 없이 Target? ❌ 불가능 Target 1개 + Scheme 여러개? ✅ 가능 (환경별 실행)

4. Configuration (빌드 설정)

Q: Configuration이 뭐야?

"어떤 옵션으로 컴파일할지" 미리 정의한 설정 묶음

Configuration용도특징
Debug개발 중최적화 ❌, 디버깅 ✅
Release배포용최적화 ✅, 디버깅 ❌
DebugRelease
최적화-Onone-O
속도느림빠름
디버깅가능어려움
빌드 시간빠름느림

Q: 커스텀 Configuration 예시?

Configurations: ├── Debug_Dev ← 개발 서버 + 디버그 ├── Debug_QA ← QA 서버 + 디버그 ├── Debug_Staging ← 스테이징 서버 + 디버그 ├── Release_Prod ← 프로덕션 서버 + 릴리즈 └── Release_QA ← QA 서버 + 릴리즈

코드에서 분기:

#if DEBUG print("디버그 모드") #else print("릴리즈 모드") #endif

Q: 전체 구조 정리?

Project │ ├── Target: MyApp │ └── 소스, 리소스, 의존성 정의 │ ├── Configurations (빌드 설정 세트) │ ├── Debug_Dev │ ├── Debug_QA │ └── Release_Prod │ └── Schemes (실행 조합) ├── MyApp-Dev = MyApp + Debug_Dev ├── MyApp-QA = MyApp + Debug_QA └── MyApp-Prod = MyApp + Release_Prod

비유

Target = 요리 (파스타)
Configuration = 조리법 (매운맛 / 순한맛)
Scheme = 주문서 (파스타 + 매운맛 + 포장)

5. dSYM과 빌드 스크립트

Q: dSYM이 뭐야?

Debug Symbol 파일 = 메모리 주소 ↔ 코드 위치 매핑

dSYM 없이:

0 MyApp 0x0000000102a3f4c8 1 MyApp 0x0000000102a3f1a0

dSYM 있으면:

0 MyApp HomeViewController.swift:142 loadData() 1 MyApp NetworkManager.swift:89 fetchUser()

Q: dSYM 업로드는 런타임에 하나?

아니요. 빌드 타임에 함.

Xcode Build Phases 순서: │ ├── 1. Compile Sources (.swift → .o) ├── 2. Link Binary (.o → 실행파일 + dSYM 생성) ├── 3. Copy Bundle Resources └── 4. Run Script (Firebase dSYM 업로드) ← 여기

모든 게 빌드 타임 안에서 일어남 (런타임 아님)

Q: dSYM 업로드 흐름?

빌드 시 (개발자 맥) ┌─────────────────┐ │ 링킹 완료 │ │ dSYM 파일 생성 │ └────────┬────────┘ │ Run Script Phase에서 업로드 ▼ ┌─────────────────┐ │ Firebase 서버 │ ← dSYM 보관 └─────────────────┘ 런타임 (사용자 기기) ┌─────────────────┐ │ 앱 크래시 발생 │ └────────┬────────┘ │ 크래시 로그 전송 ▼ ┌─────────────────┐ │ Firebase 서버 │ ← dSYM으로 주소 → 코드 변환 └────────┬────────┘ ▼ 읽을 수 있는 크래시 리포트

Q: 빌드 스크립트 종류?

스크립트실행 시점역할
SwiftLint빌드 전코드 스타일 검사
R.swift빌드 전리소스 타입 세이프 코드 생성
Firebase Crashlytics빌드 후dSYM 업로드

6. dyld와 Dynamic Framework 로딩

Q: 앱 실행 시 dyld가 미리 코드를 읽어오는가?

Static Framework (정적):

앱 실행 시: ┌─────────────────────────────────────┐ │ 앱 바이너리 (Static 코드 포함) │ │ ┌─────────────────────────────┐ │ │ │ main() │ │ │ │ HomeViewController │ │ │ │ Alamofire (Static으로 복사됨) │ │ ← 이미 앱 안에 있음 │ │ RxSwift (Static으로 복사됨) │ │ │ └─────────────────────────────┘ │ └─────────────────────────────────────┘ dyld 할 일 없음 - 이미 앱 바이너리에 포함됨

Dynamic Framework (동적):

앱 실행 시: ┌──────────────────────────────────────────────────────┐ │ 1. dyld 시작 │ │ 2. 앱 바이너리 로드 │ │ 3. Dynamic Framework 목록 확인 │ │ - UIKit.framework │ │ - MyDynamicLib.framework │ │ 4. 각 Framework를 메모리에 매핑 (mmap) │ │ 5. 심볼 주소 바인딩 (rebase + bind) │ │ 6. main() 호출 │ └──────────────────────────────────────────────────────┘

Q: 실제로 코드를 "읽어오는" 시점은?

dyld가 하는 것: - Framework 파일을 가상 메모리에 매핑 (mmap) - 심볼 주소 연결 (바인딩) 실제 코드 로딩: - 가상 메모리 주소만 할당됨 - 실제 물리 메모리 로딩은 "Page Fault" 시점 - 즉, 해당 코드를 처음 실행할 때 OS가 로드

정리

dyld: 앱 시작 시 주소 매핑 + 심볼 바인딩
실제 코드 로딩: 해당 코드 처음 실행 시 (Lazy Loading)

7. Symbol Table vs .swiftmodule

Q: Symbol Table이 뭐야?

함수 이름 ↔ 주소 매핑 (링커/디버거용)

Symbol Table (오브젝트 파일 / 바이너리 안에 존재) ┌────────────────────────────────────────┐ │ Name │ Address │ ├────────────────────────────────────────┤ │ _$s7MyClass4loadyyF │ 0x1000 │ ← load() 함수 │ _$s7MyClass4saveyyF │ 0x1200 │ ← save() 함수 │ _$s7MyClassCfd │ 0x1400 │ ← deinit └────────────────────────────────────────┘

용도:

Q: .swiftmodule이 뭐야?

모듈의 공개 인터페이스 (컴파일러용)

MyModule.swiftmodule 내용 (바이너리 형식): ┌────────────────────────────────────────┐ │ public class MyClass { │ │ public func load() -> Data │ │ public func save(_ data: Data) │ │ } │ │ │ │ public struct Config { │ │ public var timeout: Int │ │ public var retryCount: Int │ │ } │ │ │ │ Generic 정보, Associated Type 등... │ └────────────────────────────────────────┘

용도:

Q: Symbol Table vs .swiftmodule 비교?

Symbol Table.swiftmodule
위치.o, .a, .framework 바이너리 안별도 파일 (.swiftmodule)
내용이름 ↔ 주소 매핑타입, 함수 시그니처, 제네릭
용도링킹, 디버깅컴파일 (타입 체크)
사용 시점링크 타임, 런타임 (디버깅)컴파일 타임
사용자링커 (ld), 디버거 (lldb)컴파일러 (swiftc)

Q: 전체 흐름은?

A 모듈 컴파일: ┌─────────────────────────────────────────────────┐ │ A.swift │ │ ┌─────────────────────────────────────┐ │ │ │ public func hello() { print("Hi") } │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────────┘ │ ▼ ┌───────────┴───────────┐ ▼ ▼ ┌───────────────┐ ┌───────────────┐ │ A.o │ │ A.swiftmodule │ │ (오브젝트) │ │ (인터페이스) │ │ │ │ │ │ Symbol Table │ │ public func │ │ hello → 0x100│ │ hello() │ └───────────────┘ └───────────────┘ │ │ │ │ import A │ ▼ │ ┌───────────────┐ │ │ B.swift │ │ │ A.hello() 호출│ ← 타입 체크 통과 │ └───────────────┘ │ │ │ ▼ 컴파일 │ ┌───────────────┐ │ │ B.o │ │ │ hello 호출 │ ← 주소는 아직 모름 (?) │ └───────────────┘ │ │ └───────────┬───────────┘ │ 링킹 ▼ ┌───────────────┐ │ App Binary │ │ │ │ hello → 0x100│ ← Symbol Table로 주소 연결 └───────────────┘

정리

.swiftmodule: 컴파일 시 "이 함수 있어?" 확인 (타입 체크)
Symbol Table: 링크 시 "이 함수 주소가 뭐야?" 연결

8. dyld의 정확한 역할

Q: dyld가 런타임에 함수이름과 주소를 매핑하는 건가?

아니요. dyld는 이미 있는 Symbol Table을 보고 실제 메모리 주소로 연결해주는 것.

Static Framework (dyld 관여 안 함)

빌드 타임 (링커가 처리): ┌─────────────────────────────────────────┐ │ 링커가 Symbol Table 보고 주소 연결 완료 │ │ hello() → 0x1000 (고정) │ └─────────────────────────────────────────┘ │ ▼ 런타임: ┌─────────────────────────────────────────┐ │ 앱 바이너리 안에 모든 코드 + 주소 확정 │ │ hello() 호출 → 바로 0x1000으로 점프 │ │ │ │ dyld 할 일 없음 │ └─────────────────────────────────────────┘

Dynamic Framework (dyld가 처리)

빌드 타임: ┌─────────────────────────────────────────┐ │ 앱 바이너리 │ │ hello() 호출 → 주소 미정 (?) │ │ "나중에 dyld가 채워줄 자리" 표시만 해둠 │ └─────────────────────────────────────────┘ 런타임 (앱 시작 시): ┌─────────────────────────────────────────┐ │ 1. dyld가 Dynamic Framework 로드 │ │ 2. Framework가 메모리 0x5000에 올라감 │ │ 3. hello()의 실제 주소 = 0x5100 │ │ 4. 앱 바이너리의 "?" 자리에 0x5100 기록 │ ← 바인딩 └─────────────────────────────────────────┘ 실행 시: ┌─────────────────────────────────────────┐ │ hello() 호출 → 0x5100으로 점프 │ └─────────────────────────────────────────┘

그림으로 정리

Dynamic Framework 로딩 과정: 앱 바이너리 MyFramework.framework ┌─────────────┐ ┌─────────────┐ │ main() │ │ hello() │ │ ... │ │ world() │ │ call hello │──?──┐ │ ... │ │ (주소 미정) │ │ └─────────────┘ └─────────────┘ │ │ │ dyld 로드 │ │ ◀──────────┘ │ │ dyld: "hello는 0x5100이다" │ ▼ ┌─────────────┐ ┌─────────────┐ │ main() │ │ 0x5100: │ │ ... │ │ hello() │ │ call 0x5100 │──────────────▶│ 0x5200: │ │ (주소 확정) │ │ world() │ └─────────────┘ └─────────────┘

핵심 비교

빌드 타임런타임 (dyld)
Symbol Table링커가 생성 (이름↔주소)읽기만 함
주소 바인딩Static: 완료Dynamic: dyld가 수행
함수 이름바이너리에 저장됨dyld가 새로 매핑 안 함

결론

dyld가 하는 것:
✅ Dynamic Framework를 메모리에 로드
✅ 로드된 위치 기반으로 주소 바인딩 (? → 0x5100)
❌ 함수 이름 ↔ 주소 테이블 새로 생성 (이미 있음)

dyld는 "함수 이름과 주소를 매핑"하는 게 아니라, 이미 있는 Symbol Table을 보고 실제 메모리 주소로 연결해주는 것.

9. Tuist의 SPM 통합 & 캐싱 (2026-06-20)

SPM 통합 — "패키지가 아니라 sub project/framework로"

Xcode 기본은 SPM 패키지를 native package로 통합(Xcode 내부 블랙박스). Tuist는 다르게, 각 SPM 패키지를 별도 Xcode 프로젝트/framework 타겟으로 생성해 의존하게 만든다.

일반 Xcode SPMTuist
통합 방식Xcode가 native package로패키지를 Xcode 프로젝트/타겟으로 생성
빌드 산물package productframework
누가 관리Xcode 내부(블랙박스)Tuist가 명시적 그래프로

왜? Tuist가 빌드를 통제해야 ① 바이너리 캐싱을 외부 의존성에도 적용하고 ② 내 모듈도 외부도 framework로 일관 관리할 수 있기 때문.

SPM도 타겟으로 관리한다 (Tuist만의 개념 아님)

SPM도 Package.swifttarget이 있다 (Package > Product > Target). 차이는 SPM=라이브러리 빌드 모델(표현력 제한) vs Tuist=Xcode 프로젝트 생성 모델(풀 표현력: 앱·확장·스킴·설정). 둘 다 타겟 단위.

⭐ 증분 빌드 vs 바이너리 캐시 — 다른 층위

SPM/Xcode도 증분 빌드는 한다(바뀐 모듈만 재컴파일, DerivedData 재사용). 그런데 그건 Tuist 바이너리 캐시와 다르다.

증분 빌드 (SPM/Xcode)Tuist 바이너리 캐시
바뀐 모듈만 재컴파일
식별 방식타임스탬프·파일 변경콘텐츠 해시
저장DerivedData(로컬·휘발성)별도 캐시(로컬/원격)
클린 빌드 후❌ 사라짐 → 전부 재컴파일✅ 살아남아 재사용
새 머신/CI/팀 공유❌ 처음부터✅ (원격이면) 받아 재사용

일상 개발(DerivedData 유지)에선 SPM 증분 빌드로 충분. Tuist 바이너리 캐시가 빛나는 건 클린 빌드·CI·새 머신·팀 공유처럼 증분 캐시가 무력화되는 상황이다.

⚠️ 용어: DerivedData 재사용을 "바이너리 캐싱"이라 부르면 어폐. 그건 증분 빌드 캐시이고, "바이너리 캐시"는 해시 기반·영속·공유형(Tuist/Bazel)을 지칭. SPM은 binaryTarget(XCFramework 의존)은 지원하지만 자동 바이너리 캐시 시스템은 없다.

로컬 vs 원격 캐시 — 서버 필요 여부

원격 서버 필요?용도
로컬 캐시 (~/.cache/tuist)❌ 불필요내 머신 빌드 재사용
원격 캐시 (Tuist 서버/S3 등)✅ 필요팀·CI 공유

공유는 git이 아니라 별도 캐시 스토리지로(바이너리를 git에 커밋 X). CI는 Jenkins 같은 도구로 매번 클린 빌드 → 느린데, 원격 캐시를 붙이면 미리 빌드된 framework를 받아 가속. (CI = 자주 통합+자동 빌드·테스트 관행 / Jenkins = 그걸 실행하는 도구)

서버 없는 팀이면? 원격(CI/팀 공유) 캐싱은 ❌. 단 로컬 캐싱은 가능(클린·새 체크아웃에서 이득). 그리고 Tuist 자체의 가치(.xcodeproj 머지 충돌 제거·모듈화·설정 자동화)는 캐싱과 무관하게 큼 — 보통 이게 도입 1순위.

⚠️ 흔한 오해: "Tuist + SPM = 항상 xcframework?"

아니다. tuist cache를 실행해야만 xcframework로 저장된다.

상황SPM 저장 형태DerivedData 삭제 시
Xcode 기본소스다시 빌드
Tuist (cache 안 함)소스 (타겟으로 관리)다시 빌드
Tuist + tuist cache.xcframework캐시에서 복원 ✅

즉, Tuist를 쓴다고 자동으로 바이너리 캐시가 생기는 게 아니라, tuist cache 명령을 명시적으로 실행해야 SPM 의존성이 xcframework로 미리 빌드되어 저장된다.

관련 문서

컴파일·링킹·심볼·static/dynamic·중복 Q&A · Tuist 완벽 가이드

10. 빌드 결과물 · 캐시 · Framework 위치 Q&A (2026-06-29)

Q. 컴파일 결과물은 어디에 저장?

DerivedData에 저장됨:

~/Library/Developer/Xcode/DerivedData/
└── MyApp-xxxxx/
    └── Build/Products/
        ├── Debug-iphonesimulator/
        │   ├── Alamofire.framework   ← 의존성 빌드 결과
        │   └── MyApp.app             ← 앱 빌드 결과
        └── Release-iphoneos/

타겟 단위로 관리 — 각 타겟이 각자의 빌드 결과물 생성.

Q. DerivedData 삭제해도 컴파일 결과물 유지?

❌ 아니다. DerivedData 삭제 → 컴파일 결과물 전부 삭제 → 처음부터 다시 빌드.

상황DerivedData 삭제 시
Xcode 기본다시 컴파일
Tuist (cache 안 함)다시 컴파일
Tuist + tuist cache~/.tuist/Cache/에서 xcframework 복원 ✅

Q. SourcePackages/artifacts vs checkouts 차이?

폴더내용언제 생김
checkouts/소스 코드 (.swift)일반 SPM 의존성
artifacts/바이너리 (.xcframework)binaryTarget 의존성

Q. Tuist/Dependencies/에 Package.swift만 있는데?

실제 소스 코드는 여기 없음!

Q. tuist cache는 항상 xcframework로 복원?

✅ 맞다. tuist cache는 의존성을 xcframework로 미리 빌드해서 저장. 복원 시 xcframework 그대로 연결 (재컴파일 X).

Q. Debug-iphoneos/ 폴더에 Static/Dynamic 둘 다 있나?

✅ 맞다. 빌드 결과 폴더에는 타입 상관없이 모든 .framework가 존재.

구분 방법:

file SomeLib.framework/SomeLib
# Static:  "current ar archive"
# Dynamic: "Mach-O 64-bit dynamically linked shared library"

Q. .app/Frameworks/에 있는 것 = Dynamic만?

✅ 맞다.

위치포함되는 것
Debug-iphoneos/Static + Dynamic 모두
.app/Frameworks/Dynamic만 복사됨
.app/MyApp (실행파일)Static은 여기에 포함
Debug-iphoneos/
├── StaticA.framework      ← Static
├── DynamicB.framework     ← Dynamic
└── MyApp.app/
    ├── MyApp              ← 실행파일 (StaticA 코드 포함)
    └── Frameworks/
        └── DynamicB.framework  ← Dynamic만 여기로

Q. Static Framework는 앱 실행 파일에 포함?

✅ 맞다. Static은 링킹 시 앱 실행 파일에 코드가 복사됨. Dynamic은 .app/Frameworks/에 별도 파일로 존재.

11. 심화 Q&A — DerivedData · checkouts · artifacts · tuist cache (2026-06-29)

Q. DerivedData 폴더명이 MStove-abcdef123 이상한 이유?

해시값 = 프로젝트 경로 기반. 같은 이름 프로젝트 충돌 방지용.

~/Projects/AppA/MStove.xcodeproj → MStove-abc123/
~/Projects/AppB/MStove.xcodeproj → MStove-def456/

Q. checkouts은 항상 SPM 소스코드만?

✅ 맞다. SPM 의존성의 git에서 클론한 소스 코드만 저장.

Q. artifacts는 Xcode가 빌드한 결과물?

❌ 아니다. artifacts는 이미 빌드된 상태로 배포되는 바이너리 (binaryTarget).

폴더내용누가 만듦
checkouts/소스 코드git clone
artifacts/미리 빌드된 바이너리라이브러리 배포자
Build/Products/Xcode가 빌드한 결과물Xcode

Q. tuist install은 소스를 어디에 저장?

SourcePackages/checkouts 아님. Tuist는 자체 경로 사용:

~/.tuist/Dependencies/  또는
프로젝트/.build/checkouts/

Q. tuist cache 기준, 저장 위치, static/dynamic은?

기준: 프로젝트 의존성 그래프에서 캐시 가능한 모든 타겟.

저장 위치:

~/.tuist/Cache/
└── [hash]/
    ├── Alamofire.xcframework
    └── RxSwift.xcframework

static/dynamic: xcframework는 "컨테이너"일 뿐 — 안에 static 또는 dynamic 담을 수 있음. Tuist 설정에서 선택 가능.

Q. .framework가 Static/Dynamic 둘 다 될 수 있나?

✅ 맞다.

file MyLib.framework/MyLib
# Static:  "current ar archive random library"
# Dynamic: "Mach-O 64-bit dynamically linked shared library"

Q. Library vs Framework 차이?

LibraryFramework
정체컴파일된 코드만코드 + 헤더 + 리소스 + 메타데이터
Static.a (archive).framework (static)
Dynamic.dylib.framework (dynamic)
구조단일 파일폴더 (번들)
Library (.a):
  libMyLib.a  ← .o 파일들 묶은 아카이브

Framework (.framework):
  MyLib.framework/
  ├── MyLib         ← 바이너리 (static/dynamic)
  ├── Headers/      ← 헤더 파일
  ├── Modules/      ← Swift module
  ├── Resources/    ← 이미지, xib 등
  └── Info.plist    ← 메타데이터

요약: Framework = Library + 헤더 + 리소스 + 메타데이터 (구조화된 번들)

12. Package.swift 심화 Q&A — Target · Product · binaryTarget (2026-06-29)

Q. 1 Product = 1 Target = 1 Module?

기본적으로 맞다. 단, 1 Product가 여러 Target을 묶을 수도 있다:

.library(
    name: "MyProduct",
    targets: ["TargetA", "TargetB"]  // ← 2개 타겟 묶음
)

이 경우 import MyProduct하면 TargetA, TargetB 코드 둘 다 사용 가능.

Q. binaryTarget을 왜 만드나? 패키지 내 타겟끼리 의존 가능?

소스 코드 없이 xcframework만 있을 때 binaryTarget을 쓴다:

.binaryTarget(
    name: "SGSAuthGoogle",
    path: "SGSSDK/SGSAuthGoogle.xcframework"  // 로컬 xcframework
)

같은 패키지 내 타겟끼리 의존 가능:

targets: [
    .target(
        name: "MStoveSDKWrapper",
        dependencies: ["SGSAuthGoogle"]  // ← 같은 패키지의 binaryTarget
    ),
    .binaryTarget(name: "SGSAuthGoogle", path: "..."),
]
상황사용할 것
소스 코드 있음.target()
미리 빌드된 xcframework.binaryTarget()

Q. .zip 하나에 xcframework 여러 개?

1 zip = 1 xcframework. 하지만 전이 의존성이 풀리면서 여러 개 생긴다:

artifacts/에 생기는 것들:
├── SGSAuthGoogle.xcframework        ← 직접 선언
├── GoogleSignIn.xcframework         ← SGSAuthGoogle이 의존
├── GTMSessionFetcher.xcframework    ← GoogleSignIn이 의존
└── AppAuth.xcframework              ← 전이 의존성

Q. xcframework를 패키지로 감싸서 배포하는 이유?

직접 binaryTarget도 가능하지만, 버전 관리를 위해 패키지로 감싼다:

방식장점단점
.binaryTarget(path:)단순, 로컬 파일버전 관리 어려움
.binaryTarget(url:)원격 다운로드버전 태그 없음
.package(url:exact:)git 태그로 버전 관리Package.swift 필요

Q. dependencies에서 문자열 vs .product() 차이?

dependencies: [
    "MStoveSDKWrapperInterface",              // 같은 패키지 내 타겟
    .product(name: "SGSBase", package: "base"), // 다른 패키지, 이름 다름
    "SGSAuthGoogle",                          // 같은 패키지 내 binaryTarget
]
상황문법
같은 패키지 내 타겟"TargetName"
다른 패키지, 이름 같음"ProductName" (가능)
다른 패키지, 이름 다름.product(name:package:) (필수)

Q. 외부 패키지도 이름 같으면 문자열만 가능?

조건부로 가능. SPM이 추론할 수 있을 때만:

// 패키지명 = 프로덕트명 같으면
.package(url: ".../Alamofire", from: "5.0.0")
dependencies: ["Alamofire"]  // ✅ 가능

// 패키지명 ≠ 프로덕트명 다르면
.package(url: ".../auth-line", exact: "2.9.0")  // 패키지명: auth-line
// 프로덕트명: SGSAuthLine (다름!)
dependencies: [
    "SGSAuthLine"  // ❌ 안 됨
    .product(name: "SGSAuthLine", package: "auth-line")  // ✅ 필요
]
상황문자열 가능?
패키지명 = 프로덕트명✅ 가능
패키지명 ≠ 프로덕트명.product() 필수
프로덕트가 여러 개.product() 필수

Q. binaryTarget은 Package.dependencies에 없어도 사용 가능?

✅ 가능하다. 두 가지 dependencies를 구분:

let package = Package(
    dependencies: [
        // 패키지 레벨: 외부 패키지 가져오기
        .package(url: ".../auth-line", exact: "2.9.0"),
    ],
    targets: [
        .target(
            name: "MStoveSDKWrapper",
            dependencies: [
                // 타겟 레벨: 이 타겟이 의존하는 것
                "SGSAuthGoogle",  // ← 같은 패키지 내 binaryTarget (외부 아님)
            ]
        ),
        .binaryTarget(
            name: "SGSAuthGoogle",  // ← targets 배열에서 정의
            path: "SGSSDK/SGSAuthGoogle.xcframework"
        ),
    ]
)
위치역할내용
Package.dependencies외부 패키지 가져오기.package(url:...)
targets 배열이 패키지 내 타겟 정의.target(), .binaryTarget()
Target.dependencies이 타겟이 의존하는 것같은 패키지 타겟 + 외부 프로덕트

binaryTarget은 targets 배열에서 정의하면, 같은 패키지 내 다른 타겟이 의존 가능.

Q. auth-google 주석인데 binaryTarget은 어떻게 사용?

// 주석 처리됨 (원격 패키지 안 씀)
// .package(url: "https://.../auth-google", exact: "2.8.2"),

// 대신 로컬 xcframework 직접 사용
.binaryTarget(
    name: "SGSAuthGoogle",
    path: "SGSSDK/SGSAuthGoogle.xcframework"
)

원격 패키지 대신 로컬 xcframework를 직접 사용. 이유: 버전 충돌, 커스텀 빌드, 빌드 속도 등.

Q. 원격 패키지 binaryTarget vs 로컬 binaryTarget 차이?

SGSPush (원격 패키지 경유):
┌─────────────────────────────────────────────┐
│ MStoveSDKWrapper/Package.swift              │
│   dependencies: [.package(url: ".../push")] │
│                      ↓                      │
│   push 패키지의 Package.swift               │
│     targets: [.binaryTarget(url: "...zip")] │
│                      ↓                      │
│   원격에서 SGSPush.xcframework.zip 다운로드  │
└─────────────────────────────────────────────┘

SGSAuthGoogle (로컬 직접 참조):
┌─────────────────────────────────────────────┐
│ MStoveSDKWrapper/Package.swift              │
│   targets: [.binaryTarget(path: "SGSSDK/")] │
│                      ↓                      │
│   로컬 SGSAuthGoogle.xcframework 직접 사용  │
└─────────────────────────────────────────────┘
방식경로장점
원격 패키지Package.dependencies → 패키지 → binaryTarget(url:)버전 태그 관리
로컬 직접targets → binaryTarget(path:)단순, 오프라인 가능

Q. 앱에 포함되는 건 xcframework? framework?

xcframework 안에 있는 framework. xcframework는 컨테이너일 뿐:

SGSPush.xcframework/              ← 컨테이너 (빌드 후 사라짐)
├── ios-arm64/
│   └── SGSPush.framework/        ← 실제로 사용되는 것
│       └── SGSPush (바이너리)
├── ios-arm64_x86_64-simulator/
│   └── SGSPush.framework/
└── Info.plist

Static인 경우

빌드:
1. xcframework에서 현재 플랫폼 framework 선택
2. framework 코드 → 앱 실행파일에 복사 (링킹)

결과:
MyApp.app/
└── MyApp  ← SGSPush 코드가 여기 포함됨

(xcframework, framework 파일 자체는 앱에 없음)

Dynamic인 경우

빌드:
1. xcframework에서 현재 플랫폼 framework 선택
2. framework → .app/Frameworks/에 복사

결과:
MyApp.app/
├── MyApp  ← "SGSPush 필요해" 참조만
└── Frameworks/
    └── SGSPush.framework/  ← framework가 복사됨 (xcframework 아님!)

런타임:
dyld가 SGSPush.framework 로드

Q. 버전 충돌 시 왜 하나만 포함돼?

framework 파일 이름이 같아서. 타겟 이름 때문이 아님:

.app/Frameworks/
└── base.framework/    ← 폴더 이름

base 2.8.0 → base.framework
base 2.9.0 → base.framework
→ 이름 같아서 하나만 존재 가능
상황결과
SGSAuthGoogle이 base 2.8.0 기준 빌드 (Dynamic)base의 login(id:) 심볼 기대
앱이 base 2.9.0 포함.app/Frameworks/base.framework (2.9.0)
런타임login(id:) 없음 → 💥 크래시

같은 이름 framework는 하나만 존재 → 버전 불일치 시 심볼 못 찾아 크래시.

Q. xcodeproj 변환 = 컴파일/링킹?

❌ 아니다. 완전히 다른 단계.

단계하는 일결과물
tuist generate프로젝트 설정 파일 생성.xcodeproj, .xcworkspace
xcodebuild (⌘B)컴파일 + 링킹.app, .framework
1. tuist install     → 의존성 다운로드
2. tuist generate    → .xcodeproj 생성 (컴파일 X)
3. xcodebuild / ⌘B   → 컴파일 + 링킹 (여기서 빌드)

Q. tuist generate하면 자동으로 tuist cache 실행?

❌ 아니다. 별도 명령어.

명령어하는 일
tuist install의존성 다운로드
tuist cache의존성을 xcframework로 미리 빌드
tuist generate.xcodeproj 생성 (cache 있으면 사용)
tuist generate --no-cachecache 무시, 소스로만
cache 없이 generate:
  Alamofire → 소스 타겟 → 빌드 시 컴파일

cache 사용:
  tuist cache → ~/.tuist/Cache/에 Alamofire.xcframework
  tuist generate → xcframework 연결 → 컴파일 스킵

Q. tuist cache의 xcframework, 빌드하면 Static/Dynamic 어디로?

xcframework도 빌드(링킹) 시 Static/Dynamic 규칙 그대로 적용:

tuist cache 저장:
~/.tuist/Cache/
├── Alamofire.xcframework/     ← 안에 static 바이너리
└── RxSwift.xcframework/       ← 안에 dynamic 바이너리

빌드 (⌘B) 후:
MyApp.app/
├── MyApp                      ← 실행 파일 (Alamofire 코드 포함됨)
└── Frameworks/
    └── RxSwift.framework/     ← Dynamic만 여기로 복사
xcframework 내용링킹 시최종 위치
Static 바이너리앱 실행 파일에 병합MyApp.app/MyApp 안에 포함
Dynamic 바이너리별도 파일로 복사MyApp.app/Frameworks/

xcframework는 "컨테이너"일 뿐 — 안에 static이면 실행 파일에, dynamic이면 Frameworks/에. tuist cache든 소스 컴파일이든 이 규칙은 동일.

13. tuist generate 흐름 심화 Q&A (2026-06-29)

Q. cache 있으면 tuist install 안 해도 돼?

❌ 아니다. install과 cache는 다른 단계:

명령어하는 일캐시 영향
tuist install소스/바이너리 다운로드❌ 없음 (항상 다운로드)
tuist cache소스 → xcframework 빌드✅ 여기서 캐시 생성
tuist generate.xcodeproj 생성✅ 캐시 있으면 xcframework 참조
⌘B컴파일 + 링킹✅ 캐시 있으면 링킹만

캐시는 "다운로드"가 아니라 "컴파일"을 스킵하는 것.

Q. "소스 타겟으로 프로젝트 생성"이 무슨 말?

tuist generate가 의존성을 어떻게 참조하느냐의 차이:

캐시 없이 generate:
MyApp.xcworkspace
├── MyApp.xcodeproj
│   └── Targets: MyApp
├── Alamofire.xcodeproj         ← 별도 프로젝트
│   └── Targets: Alamofire      ← Xcode Target (소스 컴파일 필요)
└── RxSwift.xcodeproj
    └── Targets: RxSwift        ← Xcode Target (소스 컴파일 필요)

캐시 있으면 generate:
MyApp.xcodeproj                 ← 프로젝트 하나
└── Targets: MyApp
    └── Link Binary: Alamofire.xcframework, RxSwift.xcframework

"소스 타겟" = 의존성마다 Xcode Target이 생성되어 빌드 시 소스 컴파일 필요

Q. tuist generate가 컴파일도 해?

❌ 안 한다. generate는 프로젝트 파일만 생성:

단계하는 일컴파일?
tuist generate의존성 그래프 분석, .xcodeproj 생성
⌘B (xcodebuild).swift → .o → 실행파일

Q. "Link Binary With Libraries"가 뭐야?

Xcode Build Phases의 한 단계. 링커에게 "이 라이브러리들과 연결해라"고 지정하는 곳:

Build Phases (타겟 → Build Phases 탭):
├── Dependencies
├── Compile Sources           ← .swift → .o 컴파일
├── Link Binary With Libraries ← 여기! 링크할 라이브러리 지정
├── Copy Bundle Resources     ← 리소스 복사
└── Embed Frameworks          ← Dynamic Framework 앱에 포함

빌드 흐름에서의 역할:

1. Compile Sources
   .swift → .o (오브젝트 파일)

2. Link Binary With Libraries  ← 여기
   .o + 라이브러리들 → 실행 파일
   "Alamofire의 함수 주소 연결해줘"

3. Embed Frameworks (Dynamic만)
   Dynamic Framework를 .app/Frameworks/에 복사
타입Link Binary With LibrariesEmbed Frameworks
Static✅ 추가❌ 불필요 (코드가 실행파일에 병합)
Dynamic✅ 추가✅ 추가 (런타임에 로드해야 하니까)

Q. Xcode 프로젝트의 Frameworks는 Dynamic만?

❌ 아니다. 두 가지 "Frameworks"를 구분해야 함:

위치내용
Xcode "Link Binary With Libraries"Static + Dynamic 둘 다 참조
런타임 .app/Frameworks/Dynamic만 복사됨
Xcode 프로젝트 (빌드 설정):
└── Link Binary With Libraries:
    ├── Alamofire.xcframework  ← Static도 여기 참조
    └── RxSwift.xcframework    ← Dynamic도 여기 참조

빌드 후 .app:
MyApp.app/
├── MyApp                      ← Static은 여기에 포함
└── Frameworks/                ← Dynamic만 여기에 복사
    └── RxSwift.framework

Q. 의존성 있으면 xcworkspace가 무조건 생겨?

❌ 항상은 아니다.

상황생성되는 것
의존성 없음.xcodeproj
의존성 있음 (캐시 없음).xcworkspace + 의존성별 .xcodeproj
의존성 있음 (캐시 있음).xcodeproj만 (xcframework 참조)

여러 프로젝트가 필요하면 workspace, 하나면 project만.

14. Tuist Project.swift vs SPM Package.swift (2026-06-29)

Q. Project.swift와 Package.swift 차이?

Tuist Project.swiftSPM Package.swift
목적Xcode 프로젝트 생성Swift 패키지 정의
패키지 선언packages:dependencies:
결과물.xcodeproj / .xcworkspace패키지 자체
타겟 정의Target(...).target(...)

Q. packages:에 로컬/원격 패키지 둘 다 가능?

✅ 둘 다 가능.

// Tuist Project.swift
packages: [
    .local(path: "../Packages/MStoveBaseUtil"),        // 로컬 SPM 패키지
    .remote(url: "https://github.com/Alamofire", ...), // 원격 SPM 패키지
]

// SPM Package.swift
dependencies: [
    .package(path: "../MStoveBaseUtil"),               // 로컬
    .package(url: "https://github.com/Alamofire", ...), // 원격
]

Q. .package(product:)는 패키지? 프로덕트?

프로덕트를 사용하겠다는 것. 패키지 ≠ 프로덕트:

packages: [
    .local(path: "../MStoveBaseUtil"),  // ← 패키지 선언
]

targets: [
    Target(
        dependencies: [
            .package(product: "MStoveBaseUtil"),  // ← 패키지의 "프로덕트" 사용
        ]
    )
]

Q. .package(product:)는 "외부 패키지" 전용?

❌ "외부"가 아니라 "packages:에 선언된 패키지" 전용. 로컬이든 원격이든:

packages: [
    .local(path: "../MStoveBaseUtil"),     // 로컬 패키지
    .remote(url: "https://...", ...),      // 원격 패키지
]

// 둘 다 .package(product:)로 사용
dependencies: [
    .package(product: "MStoveBaseUtil"),   // 로컬이든
    .package(product: "Alamofire"),        // 원격이든
]

Q. Tuist 의존성 종류 정리

문법대상예시
.target(name:)같은 Project.swift 내 타겟.target(name: "MyFramework")
.project(target:path:)다른 Tuist Project.swift의 타겟.project(target: "FeatureA", path: "../Features")
.package(product:)packages:에 선언된 SPM 패키지 (로컬/원격).package(product: "Alamofire")
.external(name:)Dependencies.swift에 정의된 것 (Tuist 3.x+).external(name: "RxSwift")

Q. import는 Product? Module?

Module을 import한다. Product가 아니라:

Package
└── Product (외부 노출용)
    └── Target (빌드 단위)
        └── Module (import 대상)

// 의존성 선언 (Product 이름)
.package(product: "SGSAuth")

// 코드에서 사용 (Module 이름 = Target 이름)
import SGSAuth

보통 Product 이름 = Target 이름 = Module 이름이라 헷갈리지만, 실제 import하는 건 Module.

Q. 의존성 그래프가 뭐야?

누가 누구를 의존하는지 관계를 파악하는 것:

MStoveSDKWrapper
├── MStoveBaseUtil
├── SGSAuth
│   ├── SGSBase        ← SGSAuth가 의존
│   └── SGSLog
└── SGSPush
    └── SGSBase        ← 중복 (한 번만 빌드)

용도: 빌드 순서 결정, 중복 제거, 버전 충돌 감지

Q. Tuist/Package.swift vs Project.swift packages 차이?

Tuist/Package.swiftProject.swift packages:
내용외부 의존성 (GitHub 등)내부 모듈 (우리 코드)
사용법.external(name:).package(product:)
캐싱✅ 대상 (버전 고정)❌ 대상 아님 (자주 바뀜)
// Tuist/Package.swift — 외부 의존성
dependencies: [
    .package(url: "https://github.com/Alamofire/Alamofire", ...),
]

// Project.swift packages: — 내부 모듈
packages: [
    .local(path: "App/MStoveBaseUI"),
    .local(path: "Feature/MStoveV3Home"),
]

// Target dependencies에서
dependencies: [
    .external(name: "Alamofire"),           // ← Tuist/Package.swift
    .package(product: "MStoveBaseUI"),      // ← 로컬 패키지
]

Q. productTypes가 뭐야?

외부 의존성의 Static/Dynamic을 결정:

// Tuist/Package.swift
let packageSettings = PackageSettings(
    productTypes: [
        "Alamofire": .staticFramework,      // Static으로 빌드
        "LineSDK": .framework,              // Dynamic으로 빌드
        "FirebaseAnalytics": .staticFramework,
    ]
)

이 설정 없으면 Tuist가 기본값으로 결정.

Q. "소스 타겟" vs "xcframework 연결" 차이?

캐시 없을 때 (소스 타겟):
MyApp.xcworkspace/
├── MyApp.xcodeproj
├── Alamofire.xcodeproj    ← 소스 타겟 (컴파일 필요)
│   └── Sources/*.swift
└── RxSwift.xcodeproj      ← 소스 타겟 (컴파일 필요)
    └── Sources/*.swift
→ ⌘B 하면 Alamofire, RxSwift 소스 전부 컴파일

캐시 있을 때 (xcframework 연결):
MyApp.xcodeproj/
└── Link:
    ├── Alamofire.xcframework   ← 미리 빌드됨
    └── RxSwift.xcframework     ← 미리 빌드됨
→ ⌘B 하면 링킹만 (컴파일 스킵)

"소스 타겟" = 소스 코드(.swift)를 포함하는 Xcode Target. 빌드 시 컴파일 필요.

Q. 내부 모듈은 캐싱 안 해?

✅ 맞다. 내부 모듈은 항상 소스 타겟:

외부 의존성내부 모듈
변경 빈도거의 안 바뀜 (버전 고정)매일 바뀜 (개발 중)
캐시 효과✅ 높음❌ 낮음 (바꿀 때마다 무효화)
generate 결과xcframework 또는 소스 타겟항상 소스 타겟
tuist generate 결과:

MyApp.xcworkspace/
├── Alamofire.xcframework        ← 외부 (캐시됨, 컴파일 X)
├── RxSwift.xcframework          ← 외부 (캐시됨, 컴파일 X)
│
├── MStoveV3Home.xcodeproj       ← 내부 (소스 타겟, 매번 컴파일)
│   └── Sources/*.swift
└── MStoveNetwork.xcodeproj      ← 내부 (소스 타겟, 매번 컴파일)
    └── Sources/*.swift

Q. 전체 흐름 정리

1. Tuist/Package.swift
   → 외부 의존성 URL + 버전 선언
   → productTypes로 Static/Dynamic 지정

2. tuist install
   → 외부 의존성 소스 다운로드

3. tuist cache (선택)
   → 외부 의존성을 xcframework로 빌드
   → ~/.tuist/Cache/에 저장

4. tuist generate
   → .xcodeproj 생성
   → 외부: 캐시 있으면 xcframework, 없으면 소스 타겟
   → 내부: 항상 소스 타겟

5. Target dependencies에서
   → .external(name:) = Tuist/Package.swift
   → .package(product:) = 로컬 패키지

Q. 왜 ".external()"이라고 부르나?

Project.swift 기준으로 "외부"라는 뜻:

프로젝트 구조:
├── Tuist/
│   └── Package.swift      ← Project.swift "외부"에 정의
│
└── Projects/
    └── ABCApp/
        └── Project.swift   ← 여기 기준
            └── packages:   ← Project.swift "내부"에 정의

Q. tuist install은 Project.swift packages:도 다운로드하나?

❌ 안 합니다:

명령어Tuist/Package.swiftProject.swift packages:
tuist install✅ 다운로드❌ 무시
tuist generate연결만✅ 이때 resolve/다운로드
// Project.swift에 원격 패키지가 있다면
packages: [
    .remote(url: "https://github.com/SomeLib/SomeLib", ...),
]

tuist install   // ← SomeLib 다운로드 안 됨
tuist generate  // ← 이때 SPM이 resolve해서 다운로드

Q. resolve가 뭐야? 언제 하나?

버전 호환성 계산 + 의존성 그래프 확정:

resolve 시점generate 시
Tuist/Package.swifttuist install연결만 (이미 끝남)
Project.swift packages:tuist generateresolve + 연결
tuist install (Tuist/Package.swift 대상)
├── 1. Package.swift 읽기
├── 2. 버전 resolve (의존성 그래프 계산)
├── 3. 소스 다운로드 → Tuist/.build/checkouts/
└── 4. Package.resolved 생성 (버전 잠금)

tuist generate
└── Tuist/Package.swift 것들은 이미 resolve 끝남
    → 그냥 연결만

Q. 왜 Tuist/Package.swift를 권장하나?

resolve가 "느림 × 반복"이라서:

tuist generate 할 때마다 (Project.swift packages: 원격):

1. 원격 서버에 연결 (네트워크)
2. 패키지 manifest 확인 (Package.swift 읽기)
3. 버전 호환성 계산 (의존성 그래프)
4. 소스 다운로드 (없거나 변경 시)
5. .xcodeproj 생성

→ 매번 1~4 체크해야 함
Project.swift packages: (원격)Tuist/Package.swift
generate매번 resolve 체크이미 완료됨
빌드 시소스 컴파일xcframework 링킹만
캐싱
체감 속도느림 (30초~1분)빠름 (5초)

결론: 한 번 resolve/build 해두고 재사용 → Tuist/Package.swift + cache 권장

15. Scheme · Configuration · dSYM (2026-06-30)

Q. Scheme 구조가 뭐야?

Scheme.scheme(
    name: "QA_ABCApp",                        // 1. 스킴 이름
    buildAction: .buildAction(targets: [...]), // 2. 빌드할 타겟
    runAction: .runAction(configuration: "Debug_Qa"),    // 3. 실행(⌘R) 시 Configuration
    archiveAction: .archiveAction(configuration: "Release_Qa") // 4. 아카이브 시 Configuration
)
┌─────────────────────────────────────────────────────────┐
│ Scheme: QA_ABCApp  ← name                               │
├─────────────────────────────────────────────────────────┤
│ Build                                                   │
│   └── Target: QA_ABCApp  ← targets                      │
├─────────────────────────────────────────────────────────┤
│ Run (⌘R)                                                │
│   └── Configuration: Debug_Qa  ← runAction              │
├─────────────────────────────────────────────────────────┤
│ Archive (배포용 빌드)                                    │
│   └── Configuration: Release_Qa ← archiveAction         │
└─────────────────────────────────────────────────────────┘

Q. Debug vs Release Configuration 차이?

설정DebugRelease
Optimization Level-Onone (없음)-O (최적화)
Debug Symbols✅ 포함❌ 제거 (dSYM 별도)
Assertions✅ 활성화❌ 제거
Testability✅ 테스트 가능
Code Signing개발자 인증서배포 인증서
// Assertions 예시
assert(user != nil, "유저 없음")
// Debug: 크래시 (버그 발견)
// Release: 이 줄 자체가 사라짐

// Optimization 예시
// Debug: 코드 그대로 실행 → 브레이크포인트 정확히 멈춤
// Release: 컴파일러가 코드 재배치 → 브레이크포인트 이상하게 멈춤

Q. 환경별 Configuration은 왜 나누나?

환경실행 시 (개발)아카이브 시 (배포)
ProductionDebugRelease
QADebug_QaRelease_Qa
SandboxDebug_SandboxRelease_Sandbox
// Debug_Qa vs Release_Qa 공통
API_BASE_URL = "https://qa-api.example.com"

// Debug_Qa만
ENABLE_LOGGING = true
SHOW_DEBUG_MENU = true

// Release_Qa만
ENABLE_LOGGING = false
SHOW_DEBUG_MENU = false

Q. dSYM이 뭐야? Firebase Crashlytics랑 무슨 관계?

dSYM = 주소 ↔ 함수명 매핑 테이블. 크래시 로그를 읽을 수 있게 해줌:

크래시 발생 시 실제 기록:
0x1000a4f2c  ← 메모리 주소만 있음
0x1000a3b18
0x1000a2104

         ↓ dSYM으로 symbolicate

Firebase Crashlytics 표시:
UserService.swift:42  fetchUser()
LoginViewController.swift:128  loginButtonTapped()
AppDelegate.swift:31  application(_:didFinishLaunching:)

Q. dSYM 없으면 어떻게 되나?

dSYM 없음dSYM 있음
Crashed: com.app.main
0x1000a4f2c
0x1000a3b18
→ 뭐가 문제인지 모름
Crashed: com.app.main
UserService.fetchUser():42
LoginViewController.loginButtonTapped():128
→ 어디서 터졌는지 바로 앎

Q. dSYM 흐름 정리

1. Release 빌드 시
   ├── 앱 바이너리 (주소만, 심볼 제거됨)
   └── dSYM 파일 (주소 ↔ 함수명 매핑)

2. 앱스토어 배포
   └── 앱 바이너리만 올라감

3. Firebase에 dSYM 업로드
   └── 나중에 크래시 해석용

4. 유저 크래시 발생
   └── 주소만 Firebase로 전송: "0x1000a4f2c에서 죽음"

5. Firebase가 dSYM으로 변환 (symbolicate)
   └── "UserService.swift:42 fetchUser()에서 죽음"

핵심: Release 빌드는 심볼 제거 → dSYM 별도 보관 → 크래시 시 symbolicate

16. Framework · Library · 링킹 흐름 (2026-06-30)

Q. Static/Dynamic Framework 결과물 확장자?

둘 다 .framework:

타입확장자특징
Static Library.a바이너리만 (헤더 별도)
Dynamic Library.dylib바이너리만 (헤더 별도)
Static Framework.framework바이너리 + 헤더 + 리소스 + 메타데이터
Dynamic Framework.framework바이너리 + 헤더 + 리소스 + 메타데이터
# .framework 안의 바이너리로 구분
file MyFramework.framework/MyFramework

# Static이면:
current ar archive random library

# Dynamic이면:
Mach-O 64-bit dynamically linked shared library

핵심: .framework는 껍데기(컨테이너), 안의 바이너리가 static/dynamic 결정

Q. Static vs Dynamic 앱 번들 구조?

Static:
MyApp.app/
└── MyApp  ← 실행파일 (Alamofire 코드 포함됨)

Dynamic:
MyApp.app/
├── MyApp  ← 실행파일 (참조만: "@rpath/Alamofire")
└── Frameworks/
    └── Alamofire.framework  ← 별도 파일로 존재

Q. Dynamic Framework 링킹 흐름?

1. 컴파일 타임
   └── .swiftmodule 읽음
       → 타입 체킹 ✅
       → 주소? ❌ 없음

2. 링크 타임
   └── 실행파일에 기록:
       "Alamofire.request 심볼 필요"
       "@rpath/Alamofire.framework에서 찾아라"
       → 주소? ❌ 아직 모름 (placeholder)

3. 런타임 (앱 실행)
   └── dyld가:
       1) .app/Frameworks/Alamofire.framework 로드
       2) 메모리에 올림
       3) 심볼 테이블 읽음
       4) "request" → 0x1000a4f2c 바인딩
       → 주소? ✅ 이제 알음
단계사용하는 것하는 일
컴파일.swiftmodule타입 체킹, 문법 검사
링크.tbd / 심볼 목록"이 심볼 필요해" 기록
런타임.framework 바이너리심볼 → 주소 바인딩

차이: Static은 링크 타임에 주소 확정, Dynamic은 런타임에 dyld가 바인딩

Q. ProjectDescriptionHelpers는 사용자 정의 폴더명?

❌ Tuist 공식 기능 (이름 고정):

Tuist/
├── Config.swift
├── Package.swift
└── ProjectDescriptionHelpers/   ← Tuist가 이 이름을 인식
    └── *.swift                  ← 자동으로 모듈로 만들어줌
// Tuist/ProjectDescriptionHelpers/MyHelpers.swift
public func makeAppTarget() -> Target { ... }

// Projects/MyApp/Project.swift
import ProjectDescriptionHelpers  // ← Tuist가 자동으로 import 가능하게 해줌

let project = Project(
    targets: [makeAppTarget()]
)

폴더 이름 바꾸면 Tuist가 인식 못함. 이름 고정, 내용만 사용자 정의.

Q. Tuist 의존성 문법 — .target() vs .package()?

"어디서 가져오느냐" 기준:

문법대상
.target(name:)같은 Project.swift 내 타겟
.project(target:path:)다른 Tuist 프로젝트의 타겟
.package(product:)packages:에 선언된 SPM 패키지의 프로덕트
.external(name:)Tuist/Package.swift의 프로덕트
// Project.swift
let project = Project(
    packages: [
        .local(path: "../MyLibrary"),  // SPM 패키지
    ],
    targets: [
        Target(
            name: "MyApp",
            dependencies: [
                .target(name: "MyFramework"),     // 같은 Project.swift의 타겟
                .package(product: "MyLibrary"),  // packages:의 SPM 프로덕트
            ]
        ),
        Target(name: "MyFramework", ...),
    ]
)

핵심: SPM 패키지의 "타겟"을 직접 가져오는 건 없음. 항상 프로덕트를 통해 가져옴.

핵심 요약

개념정의
pbxproj36년 된 OpenStep plist 형식의 프로젝트 설정 파일
Target빌드 결과물 1개 (App, Test, Widget 등)
Project타겟들의 묶음 (.xcodeproj)
Workspace프로젝트들의 묶음 (.xcworkspace)
Scheme타겟 + Configuration 조합 (실행 방법)
Configuration빌드 옵션 세트 (Debug, Release 등)
dSYM크래시 로그를 읽을 수 있게 해주는 디버그 심볼
Symbol Table함수 이름 ↔ 주소 매핑 (링커/디버거용)
.swiftmodule모듈 공개 인터페이스 (컴파일러용)
dyld런타임에 Dynamic Framework 로드 및 주소 바인딩

Tuist 한 줄 요약

Tuist = Swift 코드로 위 모든 것을 정의하고
tuist generate로 .xcodeproj/.xcworkspace 자동 생성