iOS 애플리케이션 개발에서 리소스 로딩 성능은 사용자 경험에 직접적인 영향을 미칩니다. 앱이 시작될 때 대량의 이미지, 폰트, 문자열 리소스가 한꺼번에 로딩되면 시작 시간이 길어지거나 런타임에 성능 저하가 발생할 수 있습니다. SwiftGen은 코드 생성 기술을 통해 리소스 접근을 타입-세이프한 상수로 컴파일하며, 여기에 최적화된 초기화 전략을 적용하면 성능을 한층 더 끌어올릴 수 있습니다. 이 글에서는 SwiftGen으로 생성된 코드의 리소스 사전 로딩을 최적화하는 세 가지 핵심 접근 방식, 즉 설정 최적화, 템플릿 커스터마이징, 그리고 코드 분할에 대해 상세히 설명합니다.
설정 파일 최적화: 리소스 생성 범위 제어
SwiftGen의 핵심 설정 파일인 swiftgen.yml은 리소스 관리를 위한 진입점입니다. input_dir과 filter 매개변수를 적절히 설정함으로써 불필요한 리소스 파싱 및 코드 생성을 방지하고, 초기 로딩 부하를 줄일 수 있습니다.
경로 기반 리소스 분리
input_dir과 output_dir은 전역 경로 관리를 지원합니다. 리소스를 사용 시나리오에 따라 별도의 서브 디렉토리(예: LaunchAssets.xcassets 및 UIElements.xcassets)로 분리하고, 이를 배열 형태로 여러 입력 그룹으로 설정할 수 있습니다.
xcassets:
- inputs: LaunchAssets.xcassets
outputs:
- templateName: swift5
output: LaunchResources.swift
params:
enumName: AppLaunchResources # 변경된 열거형 이름
- inputs: UIElements.xcassets
outputs:
- templateName: swift5
output: UIElementsResources.swift
params:
enumName: AppUIElements # 변경된 열거형 이름
이러한 설정은 생성된 코드를 리소스 유형별로 분리하여 필요에 따라 로드할 수 있게 합니다. 예를 들어, 앱 시작 시에는 AppLaunchResources만 초기화하고, AppUIElements는 첫 화면 렌더링 이후로 지연하여 로드할 수 있습니다.
정규식을 이용한 불필요 파일 필터링
filter 매개변수는 정규 표현식을 사용하여 파일을 걸러낼 수 있도록 지원합니다. 여러 언어의 문자열을 포함하는 Localization.lproj 디렉토리의 경우, 다음 설정으로 .strings 파일만 처리하도록 지정할 수 있습니다.
strings:
inputs: Localization.lproj
filter: \.strings$ # 정규식 간소화
outputs:
- templateName: structured-swift5
output: AppLocalizations.swift # 변경된 출력 파일 이름
이 설정은 SwiftGen이 디렉토리 내의 .storyboard나 .xib 파일을 파싱하는 것을 방지하여, 생성되는 코드의 크기와 초기화 시간을 줄입니다.
템플릿 커스터마이징: 사전 로딩 로직 삽입
SwiftGen의 템플릿 시스템(Stencil 기반)은 리소스 접근 코드를 사용자 정의할 수 있도록 합니다. Stencil 템플릿을 수정하여 생성될 열거형에 사전 로딩 로직을 주입할 수 있습니다.
정적 초기화 메소드 추가
이미지 리소스 템플릿(xcassets/swift5.stencil)의 기본 생성 코드는 주로 지연 로딩 방식을 따릅니다.
enum AppImages {
static let splashLogo = ResourceImage(named: "splashLogo")
}
struct ResourceImage {
private let resourceName: String
var uiImage: UIImage {
UIImage(named: resourceName)!
}
init(named name: String) {
self.resourceName = name
}
}
템플릿을 수정하여 정적 초기화 메소드를 추가함으로써 사전 로딩을 구현할 수 있습니다.
enum AppImages {
static let splashLogo = ResourceImage(named: "splashLogo")
static let bgPattern = ResourceImage(named: "background_pattern")
static func initializeAllImages() { // 변경된 메소드 이름
_ = [splashLogo, bgPattern].map { $0.uiImage } // 변경된 프로퍼티 이름
}
}
템플릿에 initializeAllImages()와 같은 메소드를 추가하면, 앱 시작 시 주요 리소스를 능동적으로 로드할 수 있습니다. 수정된 템플릿 파일은 프로젝트 디렉토리에 위치해야 하며, templatePath를 통해 경로를 지정합니다.
xcassets:
inputs: SharedImages.xcassets
outputs:
- templatePath: CustomTemplates/image_preload.stencil # 변경된 템플릿 경로
output: SharedImageResources.swift # 변경된 출력 파일 이름
매개변수를 이용한 로딩 제어
템플릿 매개변수(params)를 활용하면 조건부 코드 생성이 가능합니다. params는 템플릿으로 사용자 정의 변수를 전달할 수 있도록 합니다.
xcassets:
inputs: AnimationFX.xcassets # 변경된 입력 경로
outputs:
- templateName: swift5
output: AnimationEffects.swift # 변경된 출력 파일 이름
params:
enableAsyncLoad: true # 변경된 매개변수 이름
asyncLoaderName: "performAsyncLoading" # 변경된 매개변수 이름
템플릿 내에서는 이 매개변수에 따라 코드를 생성합니다.
{% if param.enableAsyncLoad %} {# param 접두사 사용 #}
static func {{ param.asyncLoaderName }}() async {
for resource in [{% for item in items %} {{ item.name }},{% endfor %}] { {# 반복 변수 및 문법 변경 #}
// 비동기 리소스 로딩 로직
await resource.loadAsync() // 가상의 비동기 로드 메소드 호출
}
}
{% endif %}
이 방식은 동일한 템플릿이 다양한 리소스의 로딩 요구사항에 유연하게 대응할 수 있도록 합니다.
코드 분할: 사용 시나리오 기반의 지연 로딩
대규모 프로젝트에서는 리소스를 기능 모듈별로 분리하고(예: '설정', '마이페이지'), 필요에 따라 로딩을 제어하는 것이 효과적입니다.
모듈별 열거형 분리
리소스 열거형을 모듈별로 분리합니다.
enum SettingsResources { // 변경된 열거형 이름
static let themeSwitchIcon = SharedImage(name: "theme_switch_icon") // 변경된 변수 이름
}
enum UserProfileResources { // 변경된 열거형 이름
static let defaultAvatarImage = SharedImage(name: "default_avatar_image") // 변경된 변수 이름
}
앱 시작 시에는 핵심 모듈 리소스만 로드합니다.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
MainAppResources.loadEssentialAssets() // 변경된 열거형 및 메소드 이름
SettingsResources.loadEssentialAssets() // 변경된 열거형 및 메소드 이름
return true
}
비핵심 모듈(예: UserProfileResources)은 해당 뷰 컨트롤러가 초기화될 때 로드할 수 있습니다.
class UserProfileViewController: UIViewController { // 변경된 클래스 이름
override func viewDidLoad() {
super.viewDidLoad()
UserProfileResources.loadEssentialAssets() // 변경된 열거형 및 메소드 이름
}
}
컴파일 조건 제어
Swift의 조건부 컴파일 지시문을 활용하여 Debug 및 Release 모드에서 다른 로딩 전략을 적용할 수 있습니다. 템플릿에 다음을 추가합니다.
#if DEBUG
static func activateAllResources() { // 변경된 메소드 이름
print("개발 모드: 모든 리소스 로드 중...") // 변경된 메시지
// 테스트 목적으로 모든 리소스 활성화
}
#else
static func activateEssentialResources() { // 변경된 메소드 이름
// 프로덕션 모드에서는 핵심 리소스만 활성화
}
#endif
성능 비교 및 최적화 모범 사례
시작 시간 최적화 효과
Xcode의 Instruments 도구를 사용하여 다양한 전략 적용 후의 시작 시간을 측정할 수 있습니다. 예를 들어, 한 이커머스 애플리케이션에서 리소스 분할 및 지연 로딩을 적용한 결과, 시작 시간이 2.3초에서 1.5초로 단축되었고, 핵심 경로 리소스 로딩 시간이 62% 감소하는 효과를 보였습니다.
주요 모범 사례 체크리스트
- 설정 최적화
- 접근 빈도에 따라 리소스 디렉토리 분할
filter를 사용하여 사용되지 않는 리소스 제외- 환경 변수를 활용한 경로 주입 (예:
${PROJECT_DIR})
- 템플릿 개발
- 사용자 정의 템플릿 저장 경로:
CustomTemplates/ - 사전 로딩 메소드 명명 규칙:
loadSynchronously()(동기) 및loadAsynchronously()(비동기) - 필수 매개변수에 기본값 추가:
{% set shouldPreload = param.preload | default(false) %}
- 사용자 정의 템플릿 저장 경로:
- 코드 조직화
- 핵심 리소스 열거형:
Sources/Generated/CoreAssets.swift - 지연 로딩 모듈:
Sources/Generated/FeatureAssets/
- 핵심 리소스 열거형:
SwiftGen의 리소스 관리 기능은 타입 안전성 확보를 넘어섭니다. 위에 소개된 설정 최적화, 템플릿 커스터마이징, 그리고 코드 분할 전략을 통해 애플리케이션의 초기화 성능을 크게 향상시킬 수 있습니다. 프로젝트의 실제 리소스 분포를 고려하여, 앱 시작 시 핵심 경로에 위치한 리소스부터 사전 로딩을 적용하고 A/B 테스트를 통해 최적화 효과를 검증하는 것이 좋습니다.