본문 바로가기

iOS

[iOS] 모듈화 가보자고..🚀(2) - Base(Util), Design System 모듈화 하기 (Tuist 적용 전)

지난 1편에 내용에 적힌 계획 순서대로 진행을 하면서 기록을 남겨두고 있습니다. 🕺
계획의 첫번 째 순서는 모든 프로젝트에서 공통으로 사용할 수 있는 코드들인 Base + Uilt성 코드들과 Desgin System에서 사용되는 컴포넌트들을 각각 모듈화 하는 것 입니다. (Base 모듈 + Design System 모듈)

👉 모듈화 1편 포스트 보러가기 [iOS] 모듈화 가보자고..🚀(1) - 모듈화하게 된 배경과 진행 방법


이런 순서로 진행하고자 하는 이유는 현재 프립의 프로젝트는 단일 모듈이고 볼륨이 크기 때문에 모듈화로 접근하기 쉬운 부분부터 하나씩 진행해보기 위함입니다!
또한 모듈화를 한다고 해서 바로 Tuist를 도입하면 현재 코드에서 전반적으로 파일들의 위치도 변경되고 그 이외에 많은 환경들이 변하기 때문에 현재 코드를 베이스로 피처 작업을 진행중인 동료 개발자분과 추후에 머지해야할 때, 충돌나는 부분이 생각보다 많을 것이라 예상되어 차근차근 스며들듯이 진행해보자고 판단했습니다.

(아래 나오는 스크린샷들은 설명을 위해 별도의 테스트 프로젝트를 만들어 사용했습니다.)

 

1. Base 모듈

1-1. 모듈의 역할

Base 모듈은 하나의 프로젝트만이 아니라 다른 프로젝트에서도 공통적으로 사용하는 도메인에 속한 코드들을 모아둔 모듈입니다. 
예를 들면 이런 파일들이 속합니다.

  • BaseView: UIView를 상속받아 구현된 클래스
  • BaseViewController: UIViewController를 상속받아 구현된 클래스
  • BaseTableViewCell: UITableViewCell을 상속받아 구현된 클래스
  • ...
  • JsonUtils: Json파일을 디코딩, 인코딩하는데 사용되는 함수들이 있는 유틸성 파일
  • AlertUtils: 간단한 입력만 받아 UIAlertController를 만들어주는 유틸성 파일
  • ...
  • StringExtensions: String의 extension파일. String과 관련된 편의 기능이 있는 파일
  • UIViewExtensions: UIView의 extension파일. UIView와 관련된 편의 기능이 있는 파일

1-2. 모듈 만드는 방법

1. New -> Project 에서 Framework로 생성합니다.

2. Framework 생성 후, 메인 프로젝트에서 사용하던 Base 코드들과 유틸성 코드들을 Base 모듈로 이동시켜줍니다.
모듈내에 파일 작성 방법들은 또 다른 포스트로..찾아뵙겠습니다..🙇‍♂️(이 얘기도 한 포스트 분량으로 예상됩니다.)

이 얘기 또한 별개의 얘기지만, Base 코드를 만든 이유는 모든 View, ViewController를 비롯한 UIKit 컴포넌트 클래스에서 공통으로 처리하고 싶은 로직을 구현하기 위해서 입니다. BaseView는 UIView 생성자 안에 setup(), bindConstraints() 와 같이 역할을 명시적으로 나누어서 사용하도록 제어하기 위해 만들어졌습니다.

1-3. 모듈의 라이브러리 의존성

만약 만들고 있는 Base 모듈에서 사용하는 3rd-party 라이브러리가 있다면 어떻게 해야할까요?🤔
제 경우에는 BaseView, BaseViewController에 RxSwift의 DisposeBag변수를 선언하고 싶었습니다.
Rx를 사용하기도 했고, ReactorKit을 같이 사용해서 모든 UIVew와 UIViewController에서 DisposeBag을 사용하기 때문에 Base 클래스에서 공통으로 선언하면 편하겠다고 생각했습니다.

모듈에 SPM 라이브러리를 선언해주어 모듈 안에서도 RxSwift를 사용할 수 있도록 설정했습니다.
(이외에 필요한 다른 라이브러리들도 선언해주었습니다. Then, SnapKit)

Base 모듈에 RxSwift SPM 라이브러리를 정의해주면 BaseView에서 RxSwift를 import하여 사용할 수 있습니다.

여기까지 하여 제가 생각한 Base 모듈의 기능들을 모두 정의 했습니다.

1-4. 모듈 import 하는 방법

하나의 모듈을 만들었으니 메인 프로젝트에 넣어서 사용해봐야겠죠?
메인 프로젝트에서 프로젝트 우클릭 > Add files to "{메인 프로젝트 이름}" 을 눌러서 해당 모듈의 .xcodeproj파일을 불러옵시다.

그러면 현재 프로젝트 하위에 Base 프로젝트가 모듈로 인식되게 됩니다.
그 이후 프로젝트 > General > Frameworks, Libraries, and Embedded Content 섹션에 Base.framework를 추가해줍시다!
그러면 Embed & Sign 형태로 정의가 됩니다.

여기까지 정희하면 메인 프로젝트의 파일에서 Base 모듈을 사용할 수 있습니다.

2. Design System 모듈

2-1. 모듈의 역할

Design System 모듈 안에는 프립 디자이너분들이 정의해주신 Design System UI Component들과 리소스들이 정의되어있습니다.
(Design System 모듈을 만든 방법에 대해서는 또 다른 포스트로 작성할 예정입니다!)

2-2. 모듈의 라이브러리 의존성

모듈을 만드는 방법과 삽입하는 방법은 Base모듈과 동일하여 생략했으나 라이브러리 의존성에 관해서는 겪은 문제가 있었습니다.
DesignSystem 모듈에 구현되는 클래스들은 대부분 UIComponent로, Base 모듈의 BaseView를 상속받아 만들게될 예정입니다.
또한 컴포넌트에서 자체적으로 가지고 있는 뷰와 관련된 로직도 Rx를 통해 구현할 계획입니다.
즉, DesignSystem 모듈은 Base모듈의 의존과 Base모듈에서 사용중인 라이브러리에 대한 의존도 있는 상황입니다. 
처음에는 삽질을 하면서 이렇게 설정을 진행했었는데요, 빌드에러가 발생했습니다.

  • Base 모듈에 RxSwift, SnapKit, Then 라이브러리 추가
  • DesignSystem 모듈에 Base모듈 추가 + RxSwift, SnapKit, Then 라이브러리 추가

발생 한 에러는 라이브러리 코드가 중복되어 발생하는 오류였습니다. DesignSystem 라이브러리를 빌드하면서 의존성이 걸린 Base모듈에도 RxSwift, SnapKit, Then 라이브러리가 있는데, 별개로 동일한 3가지 라이브러리가 똑같이 존재하여 코드 중복이 발생했습니다. 이 현상을 해결하기 위해 리서치를 하던중 민소네님 글을 보고 정보를 얻어 해결했습니다.

의존하는 모듈에서 사용중인 라이브러리는 의존 받는 모듈에서 그대로 사용 가능합니다. 즉, Design System모듈에서 선언한 라이브러리들은 모두 제거해주면 된다는 의미입니다. (이미 Base에서 받았기 때문!)

위 그림처럼만 의존시키면 Design System에서도 문제 없이 빌드를 할 수 있습니다!

2-3. 모듈 import 

1-4. 모듈 import 하는 방법과 같이 메인 프로젝트에 Design System 모듈을 넣었습니다!

3. 완성!

메인 프로젝트에, Base와 Design System 모듈을 넣게되었습니다!

4. Trouble shooting

4-1. CocoaPod 과의 중복

해당 포스트에서 다루진 않았지만, 실제 프립 프로젝트를 모듈화 하면서 많은 삽질을 경험한 곳이였습니다. 기존 프로젝트는 SPM으로 옮겨간 라이브러리보다 남아있던 CocoaPod 라이브러리가 많았습니다. 그래서 라이브러리 코드가 중복으로 발생하는 케이스의 에러가 많았습니다.

  • SPM에서 라이브러리를 선언했으나, Podfile에도 동일한 라이브러리를 선언해두어 동일한 라이브러리가 두번씩 import 되는 문제
  • A라는 라이브러리에 의존하는 B라는 라이브러리가 있다고 가정했을 때, A는 라이브러리는 SPM에 선언하고, B는 Pod에 선언했을 때 A 라이브러리 코드가 중복되는 문제

두번째 문제를 발견했던 라이브러리는 카카오 로그인 라이브러리였습니다. 해당 라이브러리는 Almofire에 의존하고 있어서 Almofire를 명시적으로 정의해주지 않는다면 카카오 라이브러리 설치할 때 같이 설치되게됩니다.
저의 경우에는 Alamofire는 SPM에 선언해주고 카카오 로그인은 Podfile에 내버려두니 Alamofire가 중복으로 발생한다는 에러를 받았습니다. 이 문제를 해결하기 위해 한쪽으로 몰았습니다.

4-2. 라이브러리간의 의존성

4-1 뒷부분과 연결되는 내용입니다. Alamofire라이브러리는 여러 프로젝트에서 동일하게 사용될 것 같아 Base 모듈에 선언해주고, 카카오 로그인 라이브러리는 앱에서만 사용되므로 메인 프로젝트에 선언해주었는데, 빌드를 해보니 Alamofire가 중복이라는 에러를 얻었습니다.
마찬가지로 메인 프로젝트에는 Alamofire 선언 없이 카카오 로그인 라이브러리만 정의해주었으므로 라이브러리 의존성에 의하여 Alamofire까지 메인 프로젝트에 설치되어 Base 모듈에 설치된 Alamofire와 중복이 일어나는 현상이 발생했습니다. 이런 현상을 해결하기 위해선 라이브러리 사이의 의존성을 확인해주며 모듈을 분리해주어야 합니다.

4-3. RxTest 빌드 에러

단일 모듈 프로젝트로 구성했을 경우에는 발생하지 않지만, Rx와 관련된 라이브러리가 별도의 모듈로 이동하는 경우 테스트 코드 빌드 에러를 경험했습니다. 4-2에서 얘기한 바와 같이 라이브러리간의 의존성을 확인한 결과 RxTest는 RxSwift에 의존하고 있어 RxTest와 RxSwift를 모두 Base 모듈에 정의를 해주고 메인 프로젝트의 테스트 코드를 빌드해보니 RxTest가 다른 모듈에 있어서 빌드에러가 발생했습니다. RxTest가 메인 프로젝트에 있어야 테스트 코드 빌드가 가능했습니다.. 이 문제를 해결하기 위해서 저는 Tuist를 도입하게 되었습니다. (물론 이게 가장큰 요소는 아니지만.. 여러개의 요소 중 하나였습니다.)

이렇게 Tuist 도입하기 전 환경에서 모듈화를 진행해 보았는데요, 적혀져있는 내용과 같이 아직까지 여러가지 불편한 요소들이 남아있어서 지금 상황에서 Tuist를 도입하기로 판단했습니다. 다음 포스트에서는 기존 프로젝트에 Tuist를 도입한 내용을 적어볼까 합니다! 👋