Factory를 선언하면 Cradle이 의존성을 연결하고 graph별 수명을 관리해요.
ExampleApp · 사용 안내 · 아키텍처 · 테스트 · MIT License
Cradle은 Swift Macro를 사용해 @Provide Factory를 반환 타입과 매개변수 타입으로 연결해요. 어떤 값을 누가 만들고 언제까지 쓸지는 graph마다 정해요.
Swift tools 6.3과 iOS 17 이상 또는 macOS 10.15 이상이 필요해요.
Cradle은 Swift Package Manager에서 1.0.0 이상 버전으로 설치해요.
dependencies: [
.package(
url: "https://github.com/opficdev/Cradle.git",
from: "1.0.0"
)
]Cradle을 쓸 target에는 Cradle product를 추가해요.
.target(
name: "AppComposition",
dependencies: [
.product(name: "Cradle", package: "Cradle")
]
)이 예제에서는 UserRepository와 LoadUserUseCase를 graph가 만들고 보관해요. 화면마다 달라지는 UserID는 userProfileViewModel을 호출할 때 넘겨요.
import Cradle
struct UserID {
let rawValue: String
}
struct User {
let id: UserID
}
protocol UserRepository {
func load(id: UserID) -> User
}
struct LiveUserRepository: UserRepository {
func load(id: UserID) -> User {
User(id: id)
}
}
struct LoadUserUseCase {
let repository: any UserRepository
func execute(id: UserID) -> User {
repository.load(id: id)
}
}
struct UserProfileViewModel {
let user: User
}
@DependencyGraph
final class AppGraph {
@Provide
private func makeUserRepository() -> any UserRepository {
LiveUserRepository()
}
@Provide
private func makeLoadUserUseCase(
repository: any UserRepository
) -> LoadUserUseCase {
LoadUserUseCase(repository: repository)
}
@Provide(.transient)
private func makeUserProfileViewModel(
useCase: LoadUserUseCase,
@External userID: UserID
) -> UserProfileViewModel {
UserProfileViewModel(user: useCase.execute(id: userID))
}
}
let graph = AppGraph()
let viewModel = graph.userProfileViewModel(
userID: UserID(rawValue: "user-1")
)@Provide는 반환 타입을 기준으로 Factory를 연결해요. 기본값은 graph마다 한 번 만들고 계속 쓰며 .lazy는 생성 프로퍼티를 처음 읽을 때 한 번 만들고 .transient는 접근할 때마다 Factory를 다시 호출해요. @External은 graph가 만들 수 없는 호출 시점 값에 붙여요.
Cradle은 graph를 만들 때 누락한 등록, 중복된 등록, 순환 의존성처럼 연결할 수 없는 구성을 컴파일 단계에서 알려줘요. Factory 본문에서 하는 임의 호출이나 실행 중 상태까지 검사하지는 않아요.
테스트와 Mermaid 산출물 추가하기
CradleTesting은 DependencyOverride.mock 편의 API를 제공해요. 테스트에서 .mock을 쓰려면 같은 test target에 CradleTesting을 추가해요. graph도 선언한다면 Cradle도 추가해요. .replace를 직접 쓰는 경우에는 Cradle만 필요해요.
.testTarget(
name: "AppCompositionTests",
dependencies: [
.product(name: "Cradle", package: "Cradle"),
.product(name: "CradleTesting", package: "Cradle")
]
)CradlePlugin은 Swift에서 import하지 않아요. macOS build host에서 실행되는 Build Tool Plugin이므로 Mermaid 개발 산출물이 필요할 때만 target에 연결해요.
.target(
name: "AppComposition",
dependencies: [
.product(name: "Cradle", package: "Cradle")
],
plugins: [
.plugin(name: "CradlePlugin", package: "Cradle")
]
)CradlePlugin은 arm64 또는 x86_64 macOS build host에서만 쓸 수 있어요.
Xcode에서 Mermaid 파일 열기
CradlePlugin은 build 때 Mermaid 원본을 만들어요. 외부 Xcode 프로젝트에서 파일을 바로 열려면 먼저 CopyCradleMermaid.sh를 소비자 프로젝트의 Scripts/CopyCradleMermaid.sh로 복사해요. Mermaid가 필요한 같은 target에 CradlePlugin을 연결한 뒤 target의 마지막 Run Script 단계에서 다음 명령을 실행하고 Based on dependency analysis를 선택 해제해요.
/bin/sh "${SRCROOT}/Scripts/CopyCradleMermaid.sh"Cmd+B 뒤 소비자 프로젝트의 .cradle/DependencyGraph.mmd가 생겨요. .cradle/은 Git에 올리지 않고 앱과 라이브러리 binary에도 넣지 않아요. Xcode의 build 도구 작업 경로는 공개된 고정 경로가 아니에요. 경고가 나타나면 CopyCradleMermaid.sh의 검색 경로를 확인해요.
관리자용 SwiftPM 배포
관리자는 Actions에서 Deploy SPM workflow를 직접 실행해요. 접두사 없는 version과 선택 release_notes를 입력하면 아래 순서로 배포돼요.
version형식과 같은 tag가 이미 있는지 확인해요.- artifact, 원격 revision 소비자, 전체 test를 검증해요.
- 검증한 commit에 annotated tag를 만들고 원격 tag revision을 확인해요.
- exact version 소비자를 검증한 뒤 GitHub Release를 생성하고 게시 상태를 확인해요.
tag를 원격에 push한 뒤 exact version 소비자 검증이 실패하면 tag는 남지만 GitHub Release는 아직 생성되지 않아요. createRelease 요청 뒤 응답 확인이 실패한 경우에는 GitHub에서 Release 생성 여부를 직접 확인해요. 배포 과정에서 tag를 삭제하거나 이동하지 않으므로 원인을 고친 뒤 기존 tag를 기준으로 별도 Release 절차를 진행해요.
- ExampleApp — 단일 app target에서
sources,@External, SwiftUI@Observable과@State를 쓰는 상품 상세 예제 - 아키텍처 — Macro 확장,
CradlePlugin, Mermaid artifact 제작 경로 - 여러 모듈 workspace Mermaid — Tuist 등 여러 모듈의 선언과 input 조립 경로 분석
- DependencyGraph 안내 —
sources, 수명, actor graph,CradlePlugin, 선언 조건과 compiler diagnostic - CradleTesting 안내 —
overrides: true,.replace,.mock, graph별 테스트 대역 구성