← iOS 기술면접 — 모던 스택 5종 2 / 5

02 · Tuist · 모듈 아키텍처

Interface/Implementation 분리, 링킹 전략, 앱 시작 시간, 그리고 과분할의 역효과
진행률
0 / 0 완료
이 장에서 답할 수 있게 되는 질문
  1. 모듈 경계를 나누는 기준이 무엇인가요?
  2. Interface/Implementation 분리가 왜 빌드를 빠르게 합니까?
  3. static과 dynamic 링킹의 트레이드오프는? 실무에선 어떻게 씁니까?
  4. 모듈을 100개로 쪼개면 무조건 좋아지나요?

모듈 경계를 나누는 기준 🔥 핵심

가장 흔한 오답이 "레이어별로 나눈다"입니다 — View 모듈, ViewModel 모듈, Model 모듈처럼요. 이렇게 나누면 기능 하나를 고칠 때 모듈 세 개를 동시에 건드리게 됩니다. 경계가 변경을 막아주지 못합니다.

🔑 핵심

실무 기준은 하나로 압축됩니다 — "함께 변경되는 것끼리 묶고, 따로 변경되는 것은 나눈다." 모듈 경계는 변경의 경계여야 합니다. 이 원칙의 원전은 Parnas 1972 — 모듈을 나누는 기준에서 다룹니다.

❌ 레이어별 분할 View 모듈 ViewModel 모듈 Model 모듈 "검색 기능 수정" → 세 모듈 전부 변경 → 경계가 아무것도 막지 못함 ✅ 기능별 분할 Search View Reducer Model Booking View Reducer Model "검색 기능 수정" → Search 모듈만 변경 → Booking은 재컴파일조차 안 함 기준 — 기획이 독립적으로 움직이는 단위 = 모듈 단위
레이어는 파일 정리 방식이지 모듈 경계가 아닙니다. 모듈 경계는 "이 부분만 바뀌어도 나머지가 안전한가"로 판단합니다.

Interface / Implementation 분리 🔥 핵심

각 기능을 FeatureInterface(프로토콜만)와 FeatureImplementation(실제 코드) 두 타깃으로 나눕니다. 다른 모듈은 Interface에만 의존하고, Implementation은 앱을 조립할 때만 연결합니다.

❌ 구현끼리 직접 참조 Search Booking Detail 순환 의존 위험 하나 고치면 전부 재컴파일 ✅ Interface 경유 Search Booking (impl) BookingInterface 의존 준수 App (조립) Search는 Booking의 구현을 모른다 — 프로토콜만 안다 구현을 고쳐도 Interface가 그대로면 Search는 재컴파일되지 않는다
화살표가 전부 Interface를 향해 모입니다. 구현끼리는 서로를 볼 수 없으므로 순환 의존이 구조적으로 불가능해집니다.

왜 빌드가 빨라지는가

이게 면접에서 갈리는 지점입니다. "모듈로 나누면 병렬 빌드가 되니까"는 절반만 맞는 답입니다. 더 큰 효과는 재컴파일 전파 범위가 줄어드는 것입니다.

Booking 내부 로직 한 줄 수정 직접 참조 Booking Search Home App 전부 Interface 경유 Booking Search Home App 여기서 멈춤 Interface 시그니처가 안 바뀌면 상위 모듈은 컴파일할 이유가 없다
모듈 수가 많을수록 이 차이가 커집니다. 증분 빌드에서 체감되는 개선의 상당 부분이 여기서 나옵니다.
⚠️ 주의

공짜가 아닙니다. 프로토콜 + 구현 + 조립 코드로 보일러플레이트가 늘고, 조립 지점(앱 타깃 또는 DI 컨테이너)에 복잡도가 집중됩니다. "그래서 어디에 복잡도를 몰았나요?"가 자연스러운 꼬리질문입니다.

static vs dynamic 링킹

Static Library 모듈 A 모듈 B 모듈 C 빌드 시 앱 바이너리 전부 포함됨 ✅ 실행 빠름 (로드 없음) ❌ 링크 시간 김 ❌ 중복 포함 가능 Dynamic Framework 앱 바이너리 참조만 가짐 런타임 A.framework B.framework C.framework ✅ 증분 빌드 유리 ❌ dyld 로드 → 시작 느려짐 ❌ 개수가 많을수록 누적 실무 절충 — 개발 빌드는 dynamic(증분 속도), 릴리즈는 static/merge(시작 속도)
이 둘은 빌드 시간과 실행 시간을 맞바꾸는 선택입니다. 그래서 개발용과 배포용 설정을 다르게 가져가는 게 일반적입니다.
📝 노트

Xcode 15 / iOS 15+의 Mergeable Libraries는 이 절충을 자동화합니다. 개발 중에는 동적 프레임워크로 두어 증분 빌드를 살리고, 릴리즈 빌드에서 하나로 병합해 dyld 로드 비용을 없앱니다.

앱 시작 시간 — 모듈 수가 영향을 주는 곳 🧩 롱테일

"동적 프레임워크가 많으면 시작이 느려진다"는 말의 실체는 main()이 불리기 단계에 있습니다.

pre-main (main() 호출 전) ① dyld 로드 프레임워크 개수 ∝ ② rebase/bind 포인터 수정 ③ ObjC 런타임 클래스·카테고리 등록 ④ 초기화 실행 +load · static init main() → 첫 화면 측정 — Instruments App Launch · DYLD_PRINT_STATISTICS · MetricKit
①이 프레임워크 개수에 정비례합니다. 수십 개가 되면 체감되는 수준이 되고, 이때 static 링킹이나 Mergeable Libraries가 답이 됩니다.
개선 항목구체적 조치
dyld 로드동적 프레임워크 수 축소 · static 전환 · Mergeable Libraries
ObjC 런타임+load 대신 +initialize 또는 지연 초기화
초기화 코드서드파티 SDK 초기화를 첫 화면 이후로 미루기
첫 화면시작 경로에서 동기 네트워크·디스크 I/O 제거

과분할 — 많이 쪼갤수록 좋은 게 아니다 🔥 핵심

모듈화를 해본 사람인지 아닌지가 가장 확실하게 드러나는 질문이 "모듈을 100개로 쪼개면 무조건 좋나요?"입니다. 정답은 아니오이고, 이유를 셋 이상 대야 합니다.

모듈 개수 → 총 비용 최적 구간 모듈 너무 적음 재컴파일 범위 넓음 모듈 너무 많음 링크·그래프·dyld 비용
비용 곡선은 U자입니다. 모듈화는 "많이 쪼개기"가 아니라 변경 단위와 모듈 단위를 일치시키는 작업입니다.
과분할의 대가왜 생기나
링크 시간 증가링커가 처리할 산출물 수가 늘어남
그래프 생성 지연Tuist가 프로젝트를 생성하는 시간 자체가 늘어남
앱 시작 지연동적 프레임워크라면 dyld 로드가 개수만큼 누적
개발 경험 악화한 기능 고치는데 모듈 3~4개를 오가야 함
경계의 의미 상실항상 같이 바뀌는 모듈이라면 나눠도 아무것도 못 막음
🔑 핵심

합칠 기준도 하나로 말할 수 있어야 합니다 — "항상 함께 변경되고 독립적으로 배포되지 않는 모듈은 다시 합친다."

출처 · 참고자료