SwiftGen으로 알아보는 선언형 프로그래밍
안녕하세요! 오늘은 SwiftGen
을 통해 선언형 프로그래밍에 대해 알아보도록 하겠습니다!
문제 발생
Swift
의 Image Literal
이나 Color Literal
에 대해 아시나요?
지금 다니고 있는 회사는 리소스(이미지, 컬러)를 에셋 카탈로그(xcasset
)로 관리하고 Image Literal
나 Color Literal
을 통해 리소스를 간접적으로 접근해서 사용하고 있습니다.
그런데 얼마 전부터 알 수 없는 이유로 리터럴이 제대로 작동하지 않는 것을 발견했습니다.
원인을 찾을 수 없어서 결국 대안을 찾기로 했고, 이전에 적용해본 경험이 있는 SwiftGen
을 써보기로 했습니다.
써드 파티 도구까지 써야 하나요?
물론 Image Literal
은 UIImage(named:)
로, Color Literal
은 UIColor
로 대체할 수 있습니다.
하지만 후자의 방식은 리터럴 방식보다 단점이 있습니다.
- 에셋 카탈로그(이미지, 컬러)가 바뀌거나 삭제되어도 알 방법이 없습니다.
- 선언적이지 않습니다.
이러한 이유로 기존의 장점을 유지하면서 안정적인 기술을 찾게 되었고, SwiftGen
이 제격이라 판단했습니다.
다른 대안으로는 R.Swift
도 있는 것 같은데 사용해본 적이 없지만, 사용 방식은 SwiftGen
과 비슷할 것으로 예상됩니다.
SwiftGen
을 소개합니다
SwiftGen
은 프로젝트에 있는 리소스를 Swift
코드로 자동 생성해서 타입 안정성을 부여하는 도구입니다.
장점
다음은 제가 생각하는 SwiftGen
의 장점입니다.
- OS, 프레임워크에 상관없이 리소스를 선언적으로 사용할 수 있습니다.
- 에셋 카탈로그(이미지, 컬러)를 포함해서 폰트, 스트링 파일 등 다양한 리소스를 타입 안정성을 가지고 관리할 수 있습니다.
사용법
SwiftGen
을 사용하는 방법을 알아볼까요?
첫째, swiftgen.yml
파일을 생성하고 관리하려는 리소스 정보를 입력합니다.
xcassets:
inputs:
- PROJECT_NAME/Resources/Assets.xcassets
outputs:
- templateName: swift4
output: PROJECT_NAME/Generated/Assets.swift
params:
forceProvidesNamespaces: true
둘째, SwiftGen
을 실행합니다.
swiftgen
결과물(예: Asset.swift
, L10n.swift
등)을 프로젝트에 추가하고 다음과 같이 사용하면 됩니다.
여기까지만 해도 리소스에 타입 안정성이 부여돼서 문제는 해결됐지만 다른 문제가 생깁니다.
지금처럼 SwiftGen
을 사용하면 리소스가 변경될 때마다 터미널을 켜서 swiftgen
명령어를 입력해야 하는 귀찮은 상황이 발생합니다.
위와 같은 귀찮은 상황을 제거하기 위해 스크립트를 만들고 프로젝트의 Build Phase에 등록합니다.
swiftgen.sh
#!/bin/sh
RESOURCES_DIR_PATH="../PROJECT_NAME/Resources"
HASH_FILE_PATH="$RESOURCES_DIR_PATH/checksum.md5"
current_resources_hash=`find $RESOURCES_DIR_PATH -type f ! -path "$HASH_FILE_PATH" -print0 | sort -z | xargs -0 md5 | md5`
resources_hash_changed() {
touch $HASH_FILE_PATH
old_resources_hash=`cat $HASH_FILE_PATH`
[ "$old_resources_hash" != "$current_resources_hash" ]
}
if resources_hash_changed
then
(cd .. && swiftgen)
echo $current_resources_hash >| $HASH_FILE_PATH
fi
그러면 빌드할 때마다 체크섬 검사 후 리소스에 변경이 있었으면 SwiftGen
을 실행합니다.
제 개인 프로젝트인 FREEZY의 경우엔 이미지와 컬러의 에셋 카탈로그, 다국어화 스트링 파일을 SwiftGen
을 통해 관리하고 있습니다.
여기까지 SwiftGen
에 대해 알아보았습니다. 여기부턴 앞에서 계속 말씀드렸던 선언적이다
의 의미를 알아보도록 하겠습니다.
선언적이다
‘선언적’이라는 것은 코드가 선언형 프로그래밍을 따르고 있다는 의미로, 명령형 프로그래밍과는 대비되는 개념입니다.
프로그램이 어떤 방법으로 해야 하는지를 나타내기보다 무엇과 같은지를 설명하는 경우에 "선언형"이라고 한다. 예를 들어, 웹 페이지는 선언형인데 웹페이지는 제목, 글꼴, 본문, 그림과 같이 "무엇"이 나타나야 하는지를 묘사하는 것이지 "어떤 방법으로" 컴퓨터 화면에 페이지를 나타내야 하는지를 묘사하는 것이 아니기 때문이다.
위 내용은 위키피디아에서 인용했습니다. 선언적인 것은 설명 그대로 “어떻게” 보다는 “무엇을”에 집중하는 것입니다.
왜 선언적이어야 할까요?
이미지를 그리는 방식에 비유해보겠습니다.
명령형으로 이미지를 그리는 방식이라면 우리는 hello.png
라는 이름을 가진 이미지 파일을 UIKit
인지 AppKit
인지에 따라 UIImage
, NSImage
를 사용하는 코드를 모두 작성해야 합니다. 하지만 SwiftGen
을 사용하면 Asset.hello.image
라고만 작성하면 됩니다.
이렇게 “어떻게”를 숨기고 실제 사용하는 곳에서는 “무엇을”에만 집중하는 것은 우리에게 타입 안정성, 코드의 간결함 같은 이로운 결과를 많이 제공합니다.
실제로 선언적인 기술들
우리 주변의 선언적인 기술은 뭐가 있을까요?
iOS 개발자에게 가장 익숙한 선언적인 기술은 SwiftUI
일 것 같습니다.
SwiftUI
는 뷰를 그리기 위한 하나의 코드로 기기별(아이폰, 아이패드, 애플 워치, 아이맥 등) 상황에 맞는 뷰를 그려줍니다.
사용자에게 뷰를 어떻게 그릴지보다는 무슨 뷰를 그릴지에 더 집중할 수 있게 만들기 때문에 선언적이라고 생각합니다.
코드로 비교해볼까요? 빨갛고 동그란 X 버튼을 UIKit
과 SwiftUI
로 만들어보겠습니다.
UIKit
import UIKit
let button: UIButton = UIButton(type: .system)
button.setTitle("X", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 20, weight: .bold)
button.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
button.backgroundColor = UIColor.red
button.layer.cornerRadius = 15
button.addTarget(self, action: #selector(), for: .touchUpInside)
SwiftUI
Button("X") { }
.foregroundColor(Color.white)
.font(Font.system(size: 20, weight: .bold))
.frame(width: 30, height: 30)
.background(Color.red)
.cornerRadius(15)
어떤 코드가 더 선언적일까요?
UIKit
이라고 생각하는 분이 계실 수도 있습니다.
하지만 AppKit
까지 고려했을 때 두 배가 되는 UIKit
코드와 비교해 SwiftUI
는 그대로일 것으로 예상이 됩니다.
그런 면에서 SwiftUI
가 선언적이라 할 수 있겠습니다.
SwiftUI
가 어색하신 분들에겐 Storyboard
를 대입해서 생각해보시는 것도 좋겠네요!
마치며
오늘은 iOS 개발 중 만나게 되는 리소스를 SwiftGen
을 통해서 관리하는 방법에 대해 알아보고, 선언형 프로그래밍을 맛보는 시간을 가져보았습니다.
아마 선언형 프로그래밍을 처음 접하시는 분은 많이 헷갈리실 것 같습니다. 저도 마찬가지로 처음 접했을 때는 무엇이 선언적인가에 대해 고민을 많이 했었습니다. 답은 많겠지만 단순하게 생각하는 것이 가장 쉬운 이해 방법이라고 생각합니다!
읽어주셔서 감사하고 다음 글에서 뵙겠습니다!