본문 바로가기
Swift

The Magic of Sourcery 번역

by 고고 2021. 11. 7.

Sourcery란 Swift용 코드 생성 툴입니다.

대부분의 개발자가 "보일러 플레이트"라고 부르는 매우 반복적인 자동 생성 코드에 적합합니다.

 

Ex)

  • Equatable 구현
  • Hashable 구현
  • struct 생성자
  • Lens 구현
역주 : Mock과 Stub도 만들 수 있습니다.

 

 

간단하게 기본적인 Mac command line 앱부터 시작하겠습니다.

 

이 앱에는 간단한 타입이 있습니다.

struct Person {
    var firstName: String
    var lastName: String
    var birthDate: Date
    var age: Int {
        return Calendar.current.dateComponents([.year],
                                               from: birthDate,
                                               to: Date()).year ?? -1
    }
}

이 타입에는 세 개의 일반 프로퍼티과 하나의 read-only 프로퍼티가 있습니다.

 

Swift에서 두 Person 인스턴스가 같은지 비교하려면 Equatable이라는 프로토콜을 구현해야 합니다.

그렇게 하는 것은 쉬운 일입니다. 적어도 이 경우에는 다음과 같습니다.

extension Person: Equatable {
    static func ==(lhs: Person, rhs: Person) -> Bool {
        guard lhs.firstName == rhs.firstName else { return false }
        guard lhs.lastName == rhs.lastName else { return false }
        guard lhs.birthDate == rhs.birthDate else { return false }
        return true
    }
}

 

그러나 10개의 타입에 이 작업을 수행한다고 상상해 보세요. 50개라면? 프로젝트에 몇 개의 구조체와 클래스가 있습니까?

이것들을 지속적으로 구현하는 것은 기계적인 작업이며 실제로 돈을 받는 것이 아닙니다. 이와 같은 반복 작업의 경우 자동화할 수 있는 방법이 있습니다.

 

Sourcery를 사용하면 템플릿을 기반으로 코드를 자동 생성할 수 있습니다. 이러한 템플릿은 여러 템플릿 언어로 작성할 수 있습니다.

저는 Stencil을 사용할 것입니다.

 

Sourcery와 Stencil 템플릿의 조합은 모든 타입에 대해 Equatable 구현을 자동으로 만들 수 있습니다.

 

템플릿은 다음과 같습니다.

{% for type in types.implementing.AutoEquatable %}
// MARK: {{ type.name }} Equatable
extension {{type.name}}: Equatable {
    static func ==(lhs: {{type.name}}, rhs: {{type.name}}) -> Bool {
        {% for variable in type.storedVariables %}guard lhs.{{variable.name}} == rhs.{{variable.name}} else { return false }
        {% endfor %}
        return true
    }
}
{% endfor %}

 

중요한 라인을 살펴보겠습니다.

{% for type in types.implementing.AutoEquatable %}

여기에서는 AutoEquatable을 구현하는 프로젝트의 모든 타입을 열거하고 있습니다.

AutoEquatable은 단순히 마커 프로토콜입니다.

역주 : 마커 프로토콜이란?

클래스들의 공통 그룹명으로 프로토콜를 생성하고 아무것도 구현해놓지 않는 것.
즉, 그 클래스들이 같은 그룹으로 묶였다는 뜻입니다.  이는, 기능을 구현, 상속하겠다는 의미가아니라, 프로토콜에는 아무것도 구현하지않고, 단지 같은 클래스들끼리 그룹화하겠다는 의도입니다.
protocol AutoEquatable {}

 

 

이 전제 조건과 함께 Equatable 구현의 자동 생성을 선택하는 데 사용합니다.

자동으로 생성되도록 하려면 마커 프로토콜을 타입에 추가하기만 하면 됩니다.

extension Person: AutoEquatable {}

 

 

이러한 각 타입에 대해 해당 타입에 대한 extension과 필요한 함수를 생성합니다.

extension {{type.name}}: Equatable {
    static func ==(lhs: {{type.name}}, rhs: {{type.name}}) -> Bool {

 

이 함수에서 해당 유형의 저장된 프로퍼티 각각에 대해 비교를 수행해야 합니다.

Person의 나이와 같은 프로퍼티은 저장되지 않으므로 비교할 필요가 없습니다. 하지만 Sourcery는 우리에게 이를 가능하게 합니다.

{% for variable in type.storedVariables %}guard lhs.{{variable.name}} == rhs.{{variable.name}} else { return false }

위의 내용은 기본적으로 "이 타입에 저장된 각 변수에 대해 비교를 수행하십시오"라고 말합니다.

 

템플릿의 나머지 부분은 뻔합니다.

그러나 간결함을 위해 이 템플릿은 많은 내용이 빠져 있습니다. Sourcery에서 제공하는 샘플 템플릿을 확인하는 것을 추천합니다.

 

코드 생성 수행하기

이제 Sourcery를 사용하여 생성을 수행해야 합니다.

 

샘플 앱에서 시작하여 Sourcery 바이너리를 다운로드하고 프로젝트에 포함할 수 있습니다. 이 경우 ./Resources/sourcery에 넣었습니다. 여기에서 볼 수 있습니다.

 

이제 파일에서 Sourcery를 실행할 수 있습니다. 프로젝트의 홈 폴더(SourceryDemo.xcodeproj가 있는 폴더)에 있다고 가정하고 다음과 같이 실행합니다.

$ ./Resources/sourcery/bin/sourcery 
   --sources ./SourceryDemo 
   --templates ./SourceryDemo/Templates/ 
   --output ./SourceryDemo/Autogenerated

 

Sourcery의 마법의 결과는 여기에서 볼 수 있습니다.

// Generated using Sourcery 0.5.9 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT

// MARK: Person Equatable
extension Person: Equatable {
    static func ==(lhs: Person, rhs: Person) -> Bool {
        guard lhs.firstName == rhs.firstName else { return false }
        guard lhs.lastName == rhs.lastName else { return false }
        guard lhs.birthDate == rhs.birthDate else { return false }
        return true
    }
}

Sourcery는 우리가 원하는 것을 정확히 생성했습니다. Person에 저장된 각 프로퍼티를 비교할 func ==()입니다!

 

자동 재생

템플릿을 변경하고 결과를 매우 빠르게 확인하려는 경우 어떻게 됩니까? 템플릿과 터미널 사이를 왔다 갔다 하는 것은 지루하고 답답합니다.

 

운 좋게도 Sourcery에는 이에 대한 해결책이 있습니다. 편리한 --watch 옵션을 command line에 추가하면 템플릿과 소스 파일의 변경 사항을 지속적으로 감시하면서 열린 상태로 유지됩니다.

 

한 단계 더 나아가 변경 사항이 실시간으로 발생하는 것을 볼 수 있습니다. Visual Studio Code는 여기에서 훌륭한 도우미입니다

보시다시피 템플릿을 저장하기 위해 ⌘-S를 누르는 순간 Swift 코드가 자동으로 재생성됩니다. 

 

Visual Studio Code에는 기본적으로 Stencil 하이라이트 표시가 제공되지 않습니다. 설치하려면 ⌘-T를 누른 다음 ext install stencil 명령을 입력합니다.

사전 빌드 단계

Visual Studio Code와 함께 작성하여 템플릿을 정돈하면 여전히 손으로 새로운 것들을 생성하고 싶지 않을 것입니다.

 

새 타입을 추가하면 어떻게 될까요? Sourcery는 이를 자동으로 선택하지만 다시 실행하는 경우에만 가능합니다.

 

우리가 빌드할 때마다 Sourcery가 실행되도록 쉽게 만들 수 있습니다.

Xcode에서 사전 빌드 단계를 추가할 수 있습니다. Xcode에서 대상을 선택한 다음 Build Phases 탭을 선택합니다.

새 항목을 추가할 수 있습니다.

이제 빌드할 때마다 첫 번째 단계는 Sourcery output을 새로 고치는 것입니다.

앞으로

오늘 제가 한 것은 단순히 개념 증명에 불과했지만 월요일의 의도는 Sourcery를 실제 프로젝트에 통합하는 데 시간을 할애하는 것입니다. 

 

제 설정을 보고 싶으시다면 Github에 샘플 프로젝트를 올려 놓았습니다. 커밋 기록을 보면 RxSwift 입문서에서 했던 것처럼 각 단계를 살펴볼 수 있습니다.

 

 

 

역자 : 안녕하세요 ◠‿◠ 고고입니다. 이 글은 작성자께 허락을 받고 번역하였습니다.

원문 : https://www.caseyliss.com/2017/3/31/the-magic-of-sourcery

 

The Magic of Sourcery

Autogeneration of code is my new jam.

www.caseyliss.com

 

'Swift' 카테고리의 다른 글

Nimble library not found for -lswiftXCTest  (0) 2021.11.07

댓글