다나와 PC견적 Tuist 도입기
안녕하세요 커넥트웨이브 통합App개발팀 iOS Platform Developer로 일하고 있는 최인호입니다.
오늘은 PC견적의 대변화 중에서 Tuist 관련된 이야기를 하고자 합니다.
기존의 PC견적
다나와 PC견적 iOS앱은 2012년 4월 23일에 세상에 나와, 벌써 출시된 지 12년이 넘은 앱입니다.
다나와 PC 견적은 10만 개의 풍부한 상품 정보와 PC전문 판매점의 가격 정보를 제공하여 조립 PC의 구성부터 제품의 호환성 검사, 제품 추천 그리고 결제까지 이용할 수 있는 서비스입니다.
다나와 PC견적은 오랜 세월 동안 지나옴과 동시에 다양한 서비스가 더해지면서 다음과 같은 문제를 안고 있었습니다.
1. MVVM 구조로 ViewController가 점점 무거워지는 문제
- ViewController가 방대해짐에 따른 역할 분배가 잘되지 않는 문제가 발생하여 유지보수가 점점 힘들어지는 문제가 있었습니다.
2. Objective-C 및 Storyboard 구조에서 오는 유지보수의 문제
- Objective-C를 능숙하게 다룰 수 있는 개발자가 줄어들고 이에 따른 유지보수가 점점 힘들어지는 문제를 안고 있었습니다.
3. 팀의 규모가 커짐에 따라 Merge Request가 늦어져 Xcode Project File 유지보수 문제
-
커넥트 웨이브 App 개발팀은 최근 에누리 App 개발 부서와 다나와 App 개발 부서가 합쳐짐에 따라 팀의 규모가 커지게 되었습니다.
팀의 규모가 커짐에 따라 Merge Request가 늦어짐이 예상되었습니다.
Merge Request가 늦어지면 따라 Xcode Project 파일이 Conflict날 확률이 커져감이 예상되었습니다.
그렇기에 PC견적을 대격변 시켜 유지보수가 쉽고 일관성 있는 코드를 만들고자 했습니다.
그래서 PC견적에 다음과 같이 수정사항을 적용했습니다.
- 1. Objective-C → Swift로의 언어 전환
- 2. Clean Swift 도입
- 3. Tuist 도입
이 중 저는 오늘 Tuist에 대해서 이야기하고자 합니다.
주니어 iOS 개발자로서 경험한 Tuist의 장단점과 다나와 PC견적의 Tuist는 어떻게 구성되어 있는지
그리고 앞으로 Tuist로 무엇을 하려 하는지에 대해서 이야기하고자 합니다.
Tuist 장단점
Tuist로 가져올 수 있는 장점 아래와 같습니다.
Project Conflict 해결입니다.
iOS 개발자라면 .xcodeproj
파일이 git에서 💥Conflict💥는 경우를 많이 만나 봤을 겁니다.
Tusit의 가장 큰 장점이라면 이러한 Conflict에서 해방되는 것입니다.
왜냐하면 .xcodeproj
가 필요 없어서 git에 올라가지 않기 때문입니다!
- Tuist 프로젝트를 시작하면
.gitignore
가 자동으로 만들어지는데, 여기에 Xcode 프로젝트 파일이 포함되어 있습니다.
엥? 그럼 어떻게 프로젝트를 시작해요?
Tuist는 사용자가 작성한 설계도를 따라서 .xcodeproj
와 .xcworkspace
를 만듭니다
- 설계도라는 단어는 사실 Tuist에 나오는 용어는 아닙니다. 다만 이해를 돕기 위해서 제가 사용한 단어입니다.
위에 말씀드린 것과 같이*.xcodeproj
와 .xcworkspace
를 만드는 설계도라고 이해해 주시면 됩니다.*
tuist generate
위 명령어를 입력하면 .xcodeproj
와 .xcworkspace
가 만들어집니다.
그럼 Tuist를 모르는 사람이라면 자연스럽게 2가지 질문이 떠오릅니다.
- 설계도는 어떻게 만들어요?
- 결국 작성한 설계도가 Conflict 나는 건 똑같은 거 아니에요?
Tuist는 위 두 가지에 대한 질문을 꽤나 현명하게 풀어냈습니다.
Tuist 설계도
Tuist 설계도는 Swift 코드로 작성됩니다.
위 결론 하나로 아까 던졌던 두 가지 질문에 대해 답이 될 수 있을 것이라 생각합니다.
-
1. 설계도는 어떻게 만들어요?
→ Tuist와 비슷한 역할을 하는 xcodeGen같은 경우 yaml 파일 편집을 통해서
.xcodeproj
를 다룹니다.하지만 Tuist는 Swift를 통해서 설계도를 작성하므로 iOS개발자에게 있어서 xcodeGen에 비해서 확실히 더 낮은 문턱으로 다가옵니다.
-
2. 결국 작성한 설계도가 Conflict나는 건 똑같은 거 아니에요?
→ Tuist는 최소 3개의
.swift
파일로 설계도를 구성됩니다. 물론.swift
파일도 Conflict날 수 있습니다.하지만
.xcodeproj
파일이 깨지면 너무 거대하고 때로는 감당하기엔 힘든것에 비해서Tuist의 설계도는 Swift로 작성되었기에 일반 코드 Conflict 다루듯이 편리하게 해결할 수 있다는 것이 큰 장점입니다.
자 그럼 Tuist 설계도는 어떻게 구성하는지, 다나와 PC 견적에서는 어떻게 Tuist 설계도를 만들고 있는지 만나봅시다.
Xcode 프로젝트를 위한 Tuist 설계도 구성하기
Tuist의 공식 문서를 참고해서 mise를 통해서 tuist를 설치합니다.
Ver 4로 넘어오고 나서는 이제는 mise 설치를 권장하는 것으로 바뀌었다 합니다.
homebrew와 기타 다른 방법도 적혀있긴 하지만, 권장 방법에 따라 mise 설치를 합시다.
1️⃣ mkdir [프로젝트 이름]
2️⃣ cd [프로젝트 이름]
3️⃣ tuist init --platform ios
Tuist 설치가 끝나면 위 명령어를 순차적으로 입력하여 tuist로 iOS 프로젝트를 시작해봅시다.
이후 프로젝트 설치가 끝나면 아래와 같은 폴더 처럼 나올겁니다.
저는 DanawaTuist
라는 이름으로 프로젝트를 만들어보았습니다.
Project.swift
그리고 Tuist/*
파일들이 Tuist 설계도를 위한 파일입니다.
그리고 해당 폴더에서
tuist edit
위 명령어를 통해서 Tuist 설계도를 편집할 수 있습니다.
실제로 위 명령어를 입력하면 아래와 같이 Xcode가 열립니다.
자 그럼 지금까지 Tuist를 시작하는 방법과 설계도를 어떻게 접근하는지 알아보았습니다.
그럼 다나와 PC견적의 Tuist 설계도는 어떻게 구성되어 있는지 알아봅시다.
다나와 PC견적의 설계도
Tuist 설계도의 중심은 Project.swift
파일에 있습니다.
Project.swift
파일의 Projcet 객체를 만들어서 프로젝트를 만듭니다.
그럼 Project 객체에 어떤 파라미터가 필요한지 중요한 것 위주로 살펴봅시다.
먼저 다나와 PC견적의 Project.swift
의 일부를 보여드리겠습니다.
-
Project 코드 전문
let project = Project( name: "DanawaPC", options: .options( automaticSchemesOptions: .disabled, defaultKnownRegions: ["en", "ko"], developmentRegion: "ko" ), packages: [ .remote(url: "https://github.com/facebook/facebook-ios-sdk", requirement: .upToNextMajor(from: "14.0.0")), ], settings: .settings( base: Settings.buildSettings, configurations: [ .build(.debug), .build(.qa), .build(.product) ] ), targets: [ .target( name: "DanawaPC", destinations: [.iPhone, .macWithiPadDesign], product: .app, bundleId: "${APP_BUNDLE_ID}", deploymentTargets: .iOS("14.0"), infoPlist: .file(path: "DanawaPC/SupportingFiles/info.plist"), sources: SourceFilesList(arrayLiteral: .glob("DanawaPC/Source/ThirdPartyLib/**", compilerFlags: "-w -Xanalyzer -analyzer-disable-all-checks"), .glob("DanawaPC/Source/**/*.swift") ), resources: .resources([ "DanawaPC/SupportingFiles/GoogleService-Info.plist", headers: .headers( project: ["DanawaPC/Source/ThirdPartyLib/**", "DanawaPC/SupportingFiles/**"] ), entitlements: .file(path: .relativeToRoot("DanawaPC/Entitlements/DanawaPC.entitlements")), scripts: [.commentScript], dependencies: [ .package(product: "FacebookCore", type: .runtime), .package(product: "FacebookLogin", type: .runtime), .package(product: "FacebookShare", type: .runtime), ] ), ], schemes: [ .makeScheme(.debug, name: "Develop"), .makeScheme(.qa, name: "QA"), .makeScheme(.product, name: "Product") ], additionalFiles: [ "DanawaPC/XCConfigs/Shared.xcconfig" ] )
name & options
name
: 말 그대로 프로젝트 이름!options
: 프로젝트의 옵션을 설정합니다.
pacakges
packages: [
.remote(url: "https://github.com/facebook/facebook-ios-sdk", requirement: .upToNextMajor(from: "14.0.0")),
],
PC견적에서는 Facebook을 통한 로그인을 지원합니다. 그렇기에 Facebook Package는 필수적으로 필요합니다.
Tuist에서 Pacakage를 더하는 것은 Swift Pacakage Manager를 다루는 것과 비슷합니다.
위 사진은 SPM에서 Pacakge를 더하는 모습입니다. git 주소와 버전을 입력하는 것이 Tuist와 똑같습니다.
단지 Tuist는 코드로 표현하고 있을 뿐입니다. (사실 SPM도 코드로 추가가 가능합니다만!)
Package내에는 많은 Product들이 있습니다. 어떤 Product를 가져오는지에 대해서는 이후 Target 설정을 할 때 정하게 됩니다.
settings
settings
파라미터에는 Xcode 프로젝트 전반에 대한 설정의 설정할 수 있습니다.
다나와 PC견적에서는 Debug
, QA
, Product
3가지로 나눠서 스키마 및 설정을 관리하고 있습니다.
이 중에서 오늘은 Debug
관련되어서만 이야기하고자 합니다.
settings: .settings(
base: ["CURRENT_PROJECT_VERSION": "$(APP_BUILD)"],
configurations: [
Configuration.debug(name: .configuration("Debug"), settings: ["CODE_SIGN_STYLE": "Manual"], xcconfig: .relativeToRoot("DanawaPC/XCConfigs/App/Debug.xcconfig")),
]
),
...
additionalFiles: [
"DanawaPC/XCConfigs/Shared.xcconfig"
]
PC견적에서는 스키마와 관련 없이 모두 동일하게 관리되어야 하는 Xcode 설정은 Shared.xcconfig
파일에 관리되고 있습니다.
대표적인 것이 Project version 및 App build 입니다.
// IN Shared.xcconfig file
APP_BUILD = 3
Shared.xcconfig
파일에 있는 APP_BUILD
를 가져와서 settings-base에 설정해 놓아서 스키마 관련 없이 모두 동일하게 관리할 수 있도록 설정해 놓았습니다.
또한 Debug.xcconfig
파일에는 스키마 별로 관리되어야 하는 api key
, api basement 주소
등이 관리되고 있습니다.
PC견적에서는 제가 언급한 것 외에도 많은 것들이 .xcconfig
파일 내에서 스키마 별로 따로 혹은 동일하게 관리되고 있습니다.
targets
.target(
name: "DanawaPC",
destinations: [.iPhone, .macWithiPadDesign],
product: .app,
bundleId: "${APP_BUNDLE_ID}",
deploymentTargets: .iOS("14.0"),
infoPlist: .file(path: "DanawaPC/SupportingFiles/info.plist"),
sources: SourceFilesList(arrayLiteral: .glob("DanawaPC/Source/**/*.swift")),
resources: .resources([
"DanawaPC/Resources/**",
]),
entitlements: .file(path: .relativeToRoot("DanawaPC/Entitlements/DanawaPC.entitlements")),
scripts: [TargetScript.pre(script: /* */, name: "Comment", basedOnDependencyAnalysis: false],
dependencies: [
.package(product: "FacebookCore", type: .runtime),
.package(product: "FacebookLogin", type: .runtime),
.package(product: "FacebookShare", type: .runtime),
]
),
name
, destination
, product
, bundleID
, deploymentTargets
모두 우리가 일반적으로 Xcode - Target - General에서 설정 가능 했던 것을 여기서도 설정이 가능합니다.
또한 infoPlist
파일을 가져와서 반영할 수 있습니다.
...
infoPlist: .extendingDefault(
with: ["UILaunchScreen":
["UIColorName": "", "UIImageName": "",],
]
),
...
꼭 파일이 아니더라도 Swift 코드로도 infoPlist를 만들 수 있습니다.
source
: Target을 빌드하기 위해 필요한 모든 소스파일을 의미합니다.resource
: asset 파일들을 의미합니다.entitlements
: Xcode에 필요한 entitlement 파일들을 관리합니다.script
: Xcode 빌드 과정 중에서 추가할 수 있는 Script 소스를 의미합니다. PC견적에서는 TODO, FIXME 등 다양한 Swift 주석에 Warning이 뜨도록 수정하여 사용하고 있습니다.-
dependencies
: 위에서 packages를 설정하면서 만든 package 중에 어떤 product를 의존성 주입할지 결정하는 부분입니다.Facebook github 중 Package.swift 파일에 들어가면 실제 패키지 안에 어떤 Product들이 존재하는지 볼 수 있습니다.
PC견적에서는 Facebook pacakage 중
FacebookCore
,FacebookLogin
,FacebookShare
product들을 사용하고 있기에 3가지를 의존성 주입하고 있습니다.
schemes
스키마들을 설정할 수 있는 변수입니다.
scheme(
name: "Develop",
buildAction: .buildAction(targets: ["DanawaPC"]),
runAction: .runAction(
configuration: ConfigurationName.configuration("debug"),
arguments: .arguments(
environmentVariables: [
"OS_ACTIVITY_MODE": .environmentVariable(value: "disable", isEnabled: false)
],
launchArguments: [
.launchArgument(name: "IDEPreferLogStreaming", isEnabled: true),
.launchArgument(name: "FIRDebugEnabled", isEnabled: true)
])),
archiveAction: .archiveAction(configuration: ConfigurationName.configuration("debug"),
profileAction: .profileAction(configuration: ConfigurationName.configuration("debug"),
analyzeAction: .analyzeAction(configuration: ConfigurationName.configuration("debug"))
)
runAction에서 다양한 것들을 설정할 수 있습니다. Envicronment Variables 및 다양한 것들을 설정할 수 있습니다.
Xcode 15.3에서 간혹 나왔던 로깅 관련된 버그 때문에 PC견적에서는 IDEPreferLogStreaming
변수가 Arugument Passed On Launch
에 추가된 것을 보실 수 있습니다.
또한 Firebase Analytics 위한 FIRDDEbugEnable
변수도 추가된 것을 볼 수 있습니다.
앞으로의 PC견적 with Tuist
Tuist를 이용하면서 가장 큰 장점은 더 이상 .xcodeproj
파일이 Conflict 발생해서 애먹는 일이 없다는 것입니다. 👏👏👏
하지만 Tuist의 장점을 백분 활용하고 있냐? 라는 질문에는 아직까지는 그렇지 않다고 생각합니다.
Tuist를 잘 아시는 분들이라면, Tuist는 모듈식 프로젝트를 유지하고 최적화하는데 큰 도움이 된다는 것을 아실겁니다.
Tuist를 잘 활용한다면 다양한 Project 파일로 구성되어서 마치 블록 조립하듯 .xcodeproj
와 .xcworkspace
를 만들 수 있습니다.
아직 PC견적은 단일 프로젝트로 이루어져 있습니다. PC견적을 모듈로 하나씩 분리하는 작업을 통해 더 빠르게 앱을 빌드하고, 빠르게 개발 할 수 있는 초석을 다지려합니다.
앞으로 분리하는 과정 또한 기회가 된다면 다나와 개발 블로그에서 만나뵐 수 있으면 좋겠습니다.
감사합니다.
참고 문헌
-
https://docs.tuist.dev/en/guides/quick-start/install-tuist
-
https://docs.tuist.dev/en/