앞서 배운 기본적인 개념을 응용해서 필요한 모듈만 불러와서 사용할 수 있도록 간단하게 구현해볼예정이에요
그래프를 그렸을때 이쁘게 딱 나올수 있도록
App에서 Feature를 만들고 Feature에서
Core에있는 Logger를 사용해보도록
해보게씁니다!
시작은
디렉토리를 먼저 만들어주세요
그 디렉토리로 이동한 후 터미널로 프로젝트를생성 하면 준비는 끝납니다
tuist init --platform ios --template swiftui
프로젝트 설정을 시작해볼까요
tuist edit
프로젝트 설정관련 정의한 파일들은
ProjectDescriptioinHelper폴더에 담을거에요
다른 프로젝트 모듈에서 불러올때
import ProjectDescriptionHelpers
로 불러올 수 있어요
프로젝트를 만들어주는 함수를 정의해둘게요
extension Project {
static var organizationName: String { "ns" }
public static func create(
name: String,
packages: [Package],
settings: Settings? = Settings.projectSettings,
targets: [Target],
schemes: [Scheme]
) -> Project {
Project(
name: name,
organizationName: organizationName,
options: .options(
disableBundleAccessors: true,
disableSynthesizedResourceAccessors: true
),
packages: packages,
settings: settings,
targets: targets,
schemes: schemes
)
}
}
간단하게 organization이름을 정의해두고 사용하고
필요한값들은 선택적으로 받아오도록 구현했어요
다음으로는 타겟을 만들어주는 함수를 정의할거에요
extension Target {
public static func create(
targetName: String,
product: Product,
infoPlist: [String: InfoPlist.Value],
scripts: [TargetScript],
dependencies: [TargetDependency],
settings: Settings?
) -> Target {
Target(
name: targetName,
platform: .iOS,
product: product,
productName: targetName,
bundleId: Project.organizationName + "." + workspaceName + "." + targetName,
deploymentTarget: .iOS(targetVersion: "15.0", devices: .iphone),
infoPlist: .extendingDefault(with: infoPlist),
sources: ["Sources/**"],
resources: ["Resources/**"],
entitlements: nil,
scripts: scripts,
dependencies: dependencies,
settings: settings
)
}
public static func createWithoutResource(
targetName: String,
product: Product,
infoPlist: [String: InfoPlist.Value],
scripts: [TargetScript],
dependencies: [TargetDependency],
settings: Settings?
) -> Target {
Target(
name: targetName,
platform: .iOS,
product: product,
productName: targetName,
bundleId: Project.organizationName + "." + workspaceName + "." + targetName,
deploymentTarget: .iOS(targetVersion: "15.0", devices: .iphone),
infoPlist: .extendingDefault(with: infoPlist),
sources: ["Sources/**"],
entitlements: nil,
scripts: scripts,
dependencies: dependencies,
settings: settings
)
}
}
bundleId는 {org 이름}.{workspace 이름}.{target 이름}
으로 지정될거라 구분이 모듈별로 이쁘게 되도록 했어요
예를들어
Core 모듈이라면
ns.NSWorkspace.Core이렇게 지정되겠네요
필요한부분은 함수로 정보를 받고
공통으로쓰는건 내부에 하드코딩으로 정의해서 사용했어요
workspace정의는
private func projectNameWith(module: Module) -> Path {
return "\(workspaceName)/\(module.name)"
}
let workspace = Workspace(
name: workspaceName,
projects: [
"\(workspaceName)/\(mainProjectName)",
projectNameWith(module: .feature),
projectNameWith(module: .ui),
projectNameWith(module: .core),
projectNameWith(module: .logger)
]
)
위에서 정의하려는 App, Feature, UI, Core, Logger
모듈들을 프로젝트로 만들어줍니다
이제 모듈하나하나 구조를 정의해줄거에요
워크스페이스이름을
NSWorkspace로 설정하고
그안에 프로젝트들(모듈들) 이들어갈 거에요
먼저 NSWorkspace디렉토리를 만들어주고
Workspace파일도 같은경로에 위치시켜주세요
Project가 해당 프로젝트를 만들어주는것처럼
Workspace파일이 워크스페이스를 만들어줍니다!
그뒤에
각각 모듈에맞는 디렉토리를 만들어주고
각디렉토리마다 Project.swift파일을 만들어주세요
각 프로젝트(모듈)들을 만들어주는작업이에요
작성하면 이런구조처럼 되겠네요
각 디렉토리에
Project.swift파일을 넣어줬으면
tuist edit으로 킨 엑스코드를 끄고
다시 tuist edit을 실행시켜서
최신화된 프젝트구조에서 수정할거에요
private let dependencies: [TargetDependency] = [
.featureProject
]
private let mainTarget = Target.create(
targetName: mainProjectName,
product: .app,
infoPlist: infoPlist,
scripts: [],
dependencies: dependencies,
settings: .realSettings
)
let project = Project.create(
name: mainProjectName,
packages: [],
targets: [targetWithMode(mode)],
schemes: [schemeWithMode(mode)]
)
mainApp Target을 아래와같이 설정해주고
프로젝트를 생성하도록 정의했어요
targetWithMode함수는
입력된 mode의 값에따라 dev, rc, real을 구분하기위한 목적이라
그 값중 mainTarget이 반환된다 생각하시면되요!
스키마도 마찬가지입니다
무시하셔도됩니다!
이렇게정의하면
현재
App -> Feature상태겠죠
프로젝트에 의존성을 걸기위해 미리 정의해두고 사용하도록
사용할 프로젝트의 디팬던시를 정의해줄거에요
public extension TargetDependency {
static let coreProject: TargetDependency = .project(
target: Module.core.name,
path: "../\(Module.core.name)"
)
static let loggerProject: TargetDependency = .project(
target: Module.logger.name,
path: "../\(Module.logger.name)"
)
static let uiProject: TargetDependency = .project(
target: Module.ui.name,
path: "../\(Module.ui.name)"
)
static let featureProject: TargetDependency = .project(
target: Module.feature.name,
path: "../\(Module.feature.name)"
)
}
이제 Feature프로젝트를 정의해줍시다
let featureTarget = Target.createWithoutResource(
targetName: Module.feature.name,
product: .framework,
infoPlist: infoPlist,
scripts: [],
dependencies: [
.coreProject,
.uiProject
],
settings: nil
)
let project = Project.create(
name: Module.feature.name,
packages: [
],
targets: [
featureTarget
],
schemes: []
)
Feature는 dependencies로
core와 ui를 가지고있도록 설정했어요
여기까지는
이런그래프가 그려지겠네요!
다음으로
Core모듈을 설정해줄게요
let coreTarget = Target.createWithoutResource(
targetName: Module.core.name,
product: .framework,
infoPlist: infoPlist,
scripts: [],
dependencies: [
.loggerProject
],
settings: nil
)
let project = Project.create(
name: Module.core.name,
packages: [
],
targets: [
coreTarget
],
schemes: []
)
Core에서 Logger를 의존하도록 추가해줬어요
위와같은 그림이 완성됬겠네요!
설명은 생략됬지만
Logger, UI모듈의 타겟, 프로젝트 설정해주고
Sources, Resources 디렉토리까지 만들고
(더미파일까지 넣어야 프로젝트 구조가 잘보여요)
한번 tuist generate로 생성해볼까요
원하는데로 의존성이 잘 연결된것같네요
tuist graph를 이용해서 확인해볼 수 도있어요
계획하던데로 구조를 잘 구현했네요!
추가로
print를 이용해서
터미널에서 필요한정보를 노출시킬수도있어요
이런식으로
Mode별 분기를 하는데 현재 설정이 잘먹혔는지 확인해볼 수 있어요
이제 모듈화를 시켰으니
모듈을 사용해봅시다!
해보려고하는것은
MainApp에서
Logger모듈에 정의된 로거를 사용할거에요
MainApp은 Core모듈을 의존하고
Core모듈는 Logger모듈을 의존하도록 구현했으니까
Core에서 import Logger해서 로거를 구현해주고
MainApp에서 Core의 로거를 가져다쓰도록 해볼거에요
// Logger모듈
public enum NSLogger {
public static func log(_ text: String) {
print(text)
}
}
로거모듈에서 간단하게
print를 찍는함수를 구현해뒀어요
다른모듈에서 접근해야하니까 public접근제어자를 사용했어요
// Core 모듈
import Foundation
import Logger
public protocol NSLoggerProtocol {
func log(_ text: String)
}
public struct ALoggerImpl: NSLoggerProtocol {
public func log(_ text: String) {
NSLogger.log("🫠 "+text)
}
public init() { }
}
public struct BLoggerImpl: NSLoggerProtocol {
public func log(_ text: String) {
NSLogger.log("🐙 "+text)
}
public init() { }
}
Core모듈에서는 Main App에서 사용할 2개의 로거를 구현해뒀어요
// MainApp 모듈 App.swift
import SwiftUI
import Core
@main
struct test_App: App {
var logger: any NSLoggerProtocol {
[ALoggerImpl(), BLoggerImpl()].randomElement()!
}
var body: some Scene {
WindowGroup {
MainView(logger: logger)
}
}
}
Core모듈을 임포트해서
필요한 로거를 랜덤으로 넣어주도록하고
// MainView.swift
import SwiftUI
import Core
struct MainView: View {
let logger: any NSLoggerProtocol
var body: some View {
Text("Hello, World!")
.onAppear {
logger.log("MainView 입니다")
}
}
}
뷰에서는 뭔지모를 로거를 실행만시키면되요
로거에 맞게 이모지가 변하겠네요
마무리된 프로젝트
폴더구조입니다
'iyOmSd > Title: Swift' 카테고리의 다른 글
[Swift] Core Motion (feat. 흔들기 감지센서 개발) (0) | 2023.04.19 |
---|---|
[Swift] Pulse 네트워크 디버깅 라이브러리 (0) | 2023.03.29 |
[Swift] FCM푸시 연결부터 Postman 테스트 푸시까지 (3) | 2022.12.01 |
[Swift] Xcode Cloud(CI/CD) + Tuist(프로젝트관리툴) + dSYMs 업로드까지 자동화 배포하기 (feat. 스크립트쉘) (0) | 2022.11.11 |
[Swift] Xcode Cloud CI/CD (0) | 2022.11.01 |