도입 전 상황
1. merge conflict
4명의 사람이 함께 프로덕트를 개발하면서 다양한 충돌을 경험했습니다.
일반적인 swift 코드 내에서의 충돌의 경우는 코드를 읽어보고 팀원들과 상의하면서 수정을 하기가 그렇게 어렵지는 않았습니다.
하지만 pbxproj 파일의 충돌의 경우 swift 코드처럼 쉽게 읽으면서 conflict를 해결하기에 어려움이 있었습니다.
파일을 수정, 이동, 삭제 등을 할 때마다 팀원들과 상의를 하는 것이 불필요하게 우리의 노력을 낭비하는 것이라는 생각이 들었습니다.
2. 모듈화
또한 구현을 할 수 있는 선에서의 라이브러리들은 직접 구현을 해서 사용하고, 각각의 코드의 역할 분리를 명확하게 하기로 한 상황에서 해당 구현체들을 모듈화 해서 관리를 하고 싶었습니다.
도입 이유
위의 상황에서 파일 수정, 이동, 삭제는 develop 브랜치에서 팀원들과 모여서 진행을 하고 pull을 받아서 진행을 하자는 의견 등 다양한 의견이 나왔습니다.
그때 멘토님과 해당 상황에 대해서 말씀을 드리자 XcodeGen, Tuist 같은 선택지를 주셨고, 저희는 두가지 툴에 대해서 알아보기로 했습니다.
1. 익숙한 swift
XcodeGen에 비해서 Tuist는 Swift로 작성을 할 수 있다는 장점이 있습니다.
6주만에 프로덕트를 개발해야 하는 시간이 없는 상황에 낯선 yml로 작성을 하는 것이 아닌 Swift로 설정을 할 수 있는 것은 매력적인 포인트였습니다.
2. 프로젝트 리소스 Swift코드 자동생성
사실 도입을 하고 나서 알게 된 사실이지만 Tuist는 SwiftGen
의 역할까지 해내주는 아이였습니다.
asset과 font 등을 코드로 자동으로 생성해주는 기능이 있습니다.
public enum TrinapAsset {
public static let background = TrinapColors(name: "Background")
public static let black = TrinapColors(name: "Black")
public static let border = TrinapColors(name: "Border")
public static let disabled = TrinapColors(name: "Disabled")
...
public static let selectedChat = TrinapImages(name: "selected_chat")
public static let selectedReservation = TrinapImages(name: "selected_reservation")
public static let user = TrinapImages(name: "user")
}
위와 같이 Asset들을 enum타입으로 자동으로 만들어 주어서 String 값을 직접적으로 사용하는 수고로움을 덜어줍니다.
(XcodeGen에서는 직접 stencil 파일을 만들어서 generate 해야 함)
결론적으로 Tuist는 종합 선물세트 같은 느낌으로 저희 팀에게 다가왔습니다.
위와 같은 장점들을 보고 저희는 Tuist를 도입하기로 결정했습니다.
Tuist?
Tuist는 swift로 작성된 Xcode 프로젝트를 생성, 유지, 관리하는 command-line tool입니다.
보통의 경우 Xcode에서 파일과 프로젝트를 관리하고, Xcode가 FireSystem과 Project.pbxproj을 생성해줍니다.
Tuist는 Project.swift
와 Filesystem
을 받아서 Project.pbxproj
을 생성해줍니다.
설치
해당 명령어로 Tuist를 설치합니다.
$ bash <(curl -Ls https://install.tuist.io)
initialize
tuist init
명령어를 실행하면 xcode에서 새로운 프로젝트를 만드는 것과 동일한 역할을 하게 됩니다.
(이미 존재하는 프로젝트는 tuist migrantion
명령어를 사용해서 진행한다고 합니다. 저는 사용하지 않았습니다.)
명령어를 실행하게 되면
.
├── Plugins
│ └── Ex
│ ├── Package.swift
│ ├── Plugin.swift
│ ├── ProjectDescriptionHelpers
│ │ └── LocalHelper.swift
│ └── Sources
│ └── tuist-my-cli
│ └── main.swift
├── Project.swift
├── Targets
│ ├── Ex
│ │ ├── Resources
│ │ │ └── LaunchScreen.storyboard
│ │ ├── Sources
│ │ │ └── AppDelegate.swift
│ │ └── Tests
│ │ └── AppTests.swift
│ ├── ExKit
│ │ ├── Sources
│ │ │ └── ExKit.swift
│ │ └── Tests
│ │ └── ExKitTests.swift
│ └── ExUI
│ ├── Sources
│ │ └── ExUI.swift
│ └── Tests
│ └── ExUITests.swift
└── Tuist
├── Config.swift
└── ProjectDescriptionHelpers
└── Project+Templates.swift
다음과 같은 프로젝트 구조가 자동으로 생성됩니다.
project.swift 확인하기
tuist edit
명령어를 실행하면 프로젝트에 관련한 설정을 다루는 project.swift
를 확인할 수 있습니다.
project.swift 파일 내부를 보면
let project = Project.app(name: "Ex",
platform: .iOS,
additionalTargets: ["ExKit", "ExUI"])
다음과 같은 코드가 있는 것을 볼 수 있습니다.
Ex
라는 프로젝트가 platform은 ios, target에 ExKit
과ExUI
가 추가되어 있는 것을 볼 수 있습니다.
환경설정
전반적으로 일반적인 xcodeproject의 설정과 동일하지만 그 설정을 swift로 한다고 생각했습니다.
Project 클래스가 정의된 부분을 가면
public struct Project : Codable, Equatable {
/// The name of the project. Also, the file name of the generated Xcode project.
public let name: String
/// The name of the organization used by Xcode as copyright.
public let organizationName: String?
/// The project options.
public let options: ProjectDescription.Project.Options
/// The Swift Packages used by the project.
public let packages: [ProjectDescription.Package]
/// The targets of the project.
public let targets: [ProjectDescription.Target]
/// The custom schemes for the project. Default schemes for each target are generated by default.
public let schemes: [ProjectDescription.Scheme]
/// The build settings and configuration for the project.
public let settings: ProjectDescription.Settings?
/// The custom file header template for Xcode built-in file templates.
public let fileHeaderTemplate: ProjectDescription.FileHeaderTemplate?
/// The additional files for the project. For target's additional files, see ``Target/additionalFiles``.
public let additionalFiles: [ProjectDescription.FileElement]
/// The resource synthesizers for the project to generate accessors for resources.
public let resourceSynthesizers: [ProjectDescription.ResourceSynthesizer]
public init(name: String, organizationName: String? = nil, options: ProjectDescription.Project.Options = .options(), packages: [ProjectDescription.Package] = [], settings: ProjectDescription.Settings? = nil, targets: [ProjectDescription.Target] = [], schemes: [ProjectDescription.Scheme] = [], fileHeaderTemplate: ProjectDescription.FileHeaderTemplate? = nil, additionalFiles: [ProjectDescription.FileElement] = [], resourceSynthesizers: [ProjectDescription.ResourceSynthesizer] = .default)
...
}
다음과 같은 부분이 있습니다.
해당 부분을 하나씩 원하는 대로 설정을 해서 프로젝트를 정의해줄 수 있습니다.
1.프로젝트 생성
2.프로젝트 환경 설정 및 타깃 설정
3.각각의 타깃에 대한 환경 설정
(팩토리 패턴을 이용해서 각각의 프로퍼티들에 대한 설정을 만들어 준 후 사용하게 되면 가독성이 높게 사용할 수 있습니다.)
라이브러리 다운로드
라이브러리 dependency 설정의 경우 환경설정에 들어가므로 생략을 하겠습니다.
설정을 마친 후 바로 프로젝트를 생성하는 tuist generate
명령어를 입력하게 되면 오류가 뜨게 됩니다.
설정한 라이브러리들이 다운로드가 되어 있지 않기 때문인데요, 이 때는 tuist fetch
명령어를 통해서 사용하는 라이브러리들을 다운로드하여줍니다.
단, Tuist에서 설정을 할 경우 Static Library로 생성이 되니 주의해야 합니다.
프로젝트 생성하기
설정을 마친 후
tuist generate
명령어를 실행합니다.
명령어를 실행하면 우리가 설정했던 대로 프로젝트 파일이 생성되게 됩니다.
.
├── Derived
│ ├── InfoPlists
│ │ ├── Ex-Info.plist
│ │ ├── ExKit-Info.plist
│ │ ├── ExKitTests-Info.plist
│ │ ├── ExTests-Info.plist
│ │ ├── ExUI-Info.plist
│ │ └── ExUITests-Info.plist
│ └── Sources
│ └── TuistBundle+Ex.swift
├── Ex.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ ├── xcshareddata
│ │ ├── xcdebugger
│ │ └── xcschemes
│ │ ├── Ex.xcscheme
│ │ ├── ExKit.xcscheme
│ │ └── ExUI.xcscheme
│ └── xcuserdata
│ └── kimchansoo.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── Ex.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ ├── WorkspaceSettings.xcsettings
│ │ ├── swiftpm
│ │ │ └── configuration
│ │ └── xcschemes
│ │ └── Ex-Workspace.xcscheme
│ └── xcuserdata
│ └── kimchansoo.xcuserdatad
│ ├── UserInterfaceState.xcuserstate
│ └── xcschemes
│ └── xcschememanagement.plist
├── Plugins
│ └── Ex
│ ├── Package.swift
│ ├── Plugin.swift
│ ├── ProjectDescriptionHelpers
│ │ └── LocalHelper.swift
│ └── Sources
│ └── tuist-my-cli
│ └── main.swift
├── Project.swift
├── Targets
│ ├── Ex
│ │ ├── Resources
│ │ │ └── LaunchScreen.storyboard
│ │ ├── Sources
│ │ │ └── AppDelegate.swift
│ │ └── Tests
│ │ └── AppTests.swift
│ ├── ExKit
│ │ ├── Sources
│ │ │ └── ExKit.swift
│ │ └── Tests
│ │ └── ExKitTests.swift
│ └── ExUI
│ ├── Sources
│ │ └── ExUI.swift
│ └── Tests
│ └── ExUITests.swift
└── Tuist
├── Config.swift
└── ProjectDescriptionHelpers
└── Project+Templates.swift
tuist init
만 했을 때와의 차이점이 보이시나요?
Derived
폴더와 xcworkspace
와 xcodeproj
이 생성된 것을 볼 수 있습니다.
Derived 폴더 내부에 InfoPlists
에는 프로젝트에서 필요한 infoPlist들을 넣을 수 있고, Source
폴더 내부에는 SwiftGen을 사용했을 때처럼 asset들과 font, bundle 같은 파일이 자동으로 swift로 생성됩니다.
또한 Targets
폴더를 보시게 되면 설정했던 Target들이 들어가 있습니다.
각각의 Target 폴더는 Resource
폴더와 Source
폴더로 나눠져 있는 것을 볼 수 있습니다.
Resource
폴더에는 swift 코드가 아닌 Asset, info.plist, font 등이 들어가게 되고,
우리는 Source
폴더에서 코드를 작성하면 됩니다.
마치며
Tuist를 적용하고 나서 project 파일을 swift 코드로 쉽게 파악할 수 있었습니다.
다만, Tuist를 사용해서 편리함만 있었던 것은 아닙니다.
위에서 언급한 것처럼 Tuist에서 외부 라이브러리 설정을 하게 되면 Static Library로 설정이 되기 때문에 해당 부분에서 문제가 나기도 했습니다.
Tuist 도입에서의 장점과 단점을 파악하고, 도입할 때의 이유를 생각해서 결정하시면 좋을 것 같습니다.
Example
적용된 코드를 보고 싶으시다면 아래의 링크를 참고해주시면 감사하겠습니다.
https://github.com/boostcampwm-2022/iOS02-Trinap
참고한 아티클
'🍎 iOS > ♻️ 모듈화' 카테고리의 다른 글
Modular Architecture 실전편 (0) | 2023.04.30 |
---|---|
Modular Architecture 이론편 (0) | 2023.04.23 |