iOS Dev
[Tuist] Tuist를 활용하여 SwiftUI 클린 아키텍쳐를 적용한 모듈로 나눈 후 Github에 업로드 하기 2탄!
Mosu(정종인)
2023. 5. 7. 01:04
반응형
1탄 : https://sunrinnote.tistory.com/172
[Tuist] Tuist를 활용하여 SwiftUI 클린 아키텍쳐를 적용한 모듈로 나눈 후 Github에 업로드 하기
Tuist라는 툴이 있다. 모듈화, 패키지 의존 관리, 타겟 관리 등등을 지원해준다고 한다. Tuist 자체를 소개해주는 블로그는 많이 있으니 건너뛴다. https://nsios.tistory.com/183 https://nsios.tistory.com/195 목표
sunrinnote.tistory.com
자동완성이 안되는 이유를 알았다. 그것은.. public을 안줘서..ㅠ
tuist edit으로 edit 페이지에 들어간 다음 Tuist/ProjectDescriptionHelpers/Project+Templates 코드를 다음과 같이 수정한다.
import ProjectDescription
/// Project helpers are functions that simplify the way you define your project.
/// Share code to create targets, settings, dependencies,
/// Create your own conventions, e.g: a func that makes sure all shared targets are "static frameworks"
/// See https://docs.tuist.io/guides/helpers/
public enum ModuleType: String, CaseIterable {
case DoForestApp
case DoForestCommon
case DoForestCommonUI
case DoForestData
case DoForestDomain
case DoForestEntity
case DoForestMain
case DoForestRoomDetail
case DoForestSetting
var dependencies: [TargetDependency] {
switch self {
case .DoForestApp:
return [
.with(.DoForestMain),
.with(.DoForestRoomDetail),
.with(.DoForestSetting),
.with(.DoForestData)
]
case .DoForestCommon:
return []
case .DoForestCommonUI:
return []
case .DoForestData:
return [
.with(.DoForestDomain)
]
case .DoForestDomain:
return [
.with(.DoForestEntity)
]
case .DoForestEntity:
return []
case .DoForestMain:
return [
.with(.DoForestDomain),
.with(.DoForestCommon),
.with(.DoForestCommonUI)
]
case .DoForestRoomDetail:
return [
.with(.DoForestDomain),
.with(.DoForestCommon),
.with(.DoForestCommonUI)
]
case .DoForestSetting:
return [
.with(.DoForestDomain),
.with(.DoForestCommon),
.with(.DoForestCommonUI)
]
}
}
}
private let infoPlist: [String: InfoPlist.Value] = [
"CFBundleShortVersionString": "1.0",
"CFBundleVersion": "1",
"UIMainStoryboardFile": "",
"UILaunchStoryboardName": "LaunchScreen"
]
public extension Project {
/// Helper function to create the Project for this ExampleApp
static func app(name: String, platform: Platform, additionalTargets: [String]) -> Project {
var targets = makeAppTargets(name: name,
platform: platform,
dependencies: additionalTargets.map { TargetDependency.target(name: $0) })
targets += additionalTargets.flatMap({ makeFrameworkTargets(name: $0, platform: platform) })
return Project(name: name,
organizationName: "yourssu.com",
targets: targets)
}
static func app(_ type: ModuleType) -> Project {
let name = type.rawValue
let dependencies = type.dependencies
return Project(name: name,
organizationName: "yourssu.com",
targets: [
Target(name: name,
platform: .iOS,
product: .app,
bundleId: "com.yourssu.\(name)",
infoPlist: .extendingDefault(with: infoPlist),
sources: ["Sources/**"],
resources: ["Resources/**"],
dependencies: dependencies
)
])
}
static func framework(_ type: ModuleType) -> Project {
let name = type.rawValue
let dependencies = type.dependencies
return Project(name: name,
organizationName: "yourssu.com",
targets: [
Target(name: name,
platform: .iOS,
product: .framework,
bundleId: "com.yourssu.\(name)",
infoPlist: .extendingDefault(with: infoPlist),
sources: ["Sources/**"],
resources: ["Resources/**"],
dependencies: dependencies
)
])
}
// MARK: - Private
/// Helper function to create a framework target and an associated unit test target
private static func makeFrameworkTargets(name: String, platform: Platform) -> [Target] {
let sources = Target(name: name,
platform: platform,
product: .framework,
bundleId: "com.yourssu.\(name)",
infoPlist: .default,
sources: ["Targets/\(name)/Sources/**"],
resources: [],
dependencies: [])
let tests = Target(name: "\(name)Tests",
platform: platform,
product: .unitTests,
bundleId: "com.yourssu.\(name)Tests",
infoPlist: .default,
sources: ["Targets/\(name)/Tests/**"],
resources: [],
dependencies: [.target(name: name)])
return [sources, tests]
}
/// Helper function to create the application target and the unit test target.
private static func makeAppTargets(name: String, platform: Platform, dependencies: [TargetDependency]) -> [Target] {
let platform: Platform = platform
let infoPlist: [String: InfoPlist.Value] = [
"CFBundleShortVersionString": "1.0",
"CFBundleVersion": "1",
"UIMainStoryboardFile": "",
"UILaunchStoryboardName": "LaunchScreen"
]
let mainTarget = Target(
name: name,
platform: platform,
product: .app,
bundleId: "com.yourssu.\(name)",
infoPlist: .extendingDefault(with: infoPlist),
sources: ["Targets/\(name)/Sources/**"],
resources: ["Targets/\(name)/Resources/**"],
dependencies: dependencies
)
let testTarget = Target(
name: "\(name)Tests",
platform: platform,
product: .unitTests,
bundleId: "com.yourssu.\(name)Tests",
infoPlist: .default,
sources: ["Targets/\(name)/Tests/**"],
dependencies: [
.target(name: "\(name)")
])
return [mainTarget, testTarget]
}
}
public extension TargetDependency {
static func with(_ type: ModuleType) -> Self {
let name = type.rawValue
return .project(target: name, path: .relativeToRoot("DoForest/\(name)"))
}
}
이제 각 모듈 안에 있는 Project.swift 파일을 모조리 수정한다.
App모듈인 DoForestApp 폴더 안에는 다음과 같이.
//
// Project.swift
// ProjectDescriptionHelpers
//
// Created by 정종인 on 2023/04/03.
//
import Foundation
import ProjectDescription
import ProjectDescriptionHelpers
let project = Project.app(.DoForestApp)
Framework 모듈인 DoForestMain에는 다음과 같이.
import Foundation
import ProjectDescription
import ProjectDescriptionHelpers
let project = Project.framework(.DoForestMain)
좋다 좋다!
tuist graph로 모듈 구조를 한번 보자.
이렇게 생겼는데, 여기서 DoForestCommon을 DoForestDomain에 연결할 것이다.
ModuleType을 다음과 같이 수정해준다. 다른거 바꿀 필요 없이 이것만 바꾸면 적용이 된다!!
public enum ModuleType: String, CaseIterable {
case DoForestApp
case DoForestCommon
case DoForestCommonUI
case DoForestData
case DoForestDomain
case DoForestEntity
case DoForestMain
case DoForestRoomDetail
case DoForestSetting
var dependencies: [TargetDependency] {
switch self {
case .DoForestApp:
return [
.with(.DoForestMain),
.with(.DoForestRoomDetail),
.with(.DoForestSetting),
.with(.DoForestData)
]
case .DoForestCommon:
return [
.with(.DoForestDomain)
]
case .DoForestCommonUI:
return []
case .DoForestData:
return [
.with(.DoForestDomain)
]
case .DoForestDomain:
return [
.with(.DoForestEntity)
]
case .DoForestEntity:
return []
case .DoForestMain:
return [
.with(.DoForestCommon),
.with(.DoForestCommonUI)
]
case .DoForestRoomDetail:
return [
.with(.DoForestCommon),
.with(.DoForestCommonUI)
]
case .DoForestSetting:
return [
.with(.DoForestCommon),
.with(.DoForestCommonUI)
]
}
}
}
이런 모듈 구조가 되었다. 굳굳!!
추가로, Workspace.swift도 수정해주자.
import ProjectDescription
import ProjectDescriptionHelpers
private let projects: [Path] = ModuleType.allCases.map { moduleType in
"DoForest/\(moduleType.rawValue)"
}
let workspace = Workspace(
name: "DoForest",
projects: projects
)
전체 코드:
반응형