SwiftGen을 활용한 리소스 사전 로딩 전략: 코드 생성 최적화 방안

iOS 애플리케이션 개발에서 리소스 로딩 성능은 사용자 경험에 직접적인 영향을 미칩니다. 앱이 시작될 때 대량의 이미지, 폰트, 문자열 리소스가 한꺼번에 로딩되면 시작 시간이 길어지거나 런타임에 성능 저하가 발생할 수 있습니다. SwiftGen은 코드 생성 기술을 통해 리소스 접근을 타입-세이프한 상수로 컴파일하며, 여기에 최적화된 초기화 전략을 적용하면 성능을 한층 더 끌어올릴 수 있습니다. 이 글에서는 SwiftGen으로 생성된 코드의 리소스 사전 로딩을 최적화하는 세 가지 핵심 접근 방식, 즉 설정 최적화, 템플릿 커스터마이징, 그리고 코드 분할에 대해 상세히 설명합니다.

설정 파일 최적화: 리소스 생성 범위 제어

SwiftGen의 핵심 설정 파일인 swiftgen.yml은 리소스 관리를 위한 진입점입니다. input_dirfilter 매개변수를 적절히 설정함으로써 불필요한 리소스 파싱 및 코드 생성을 방지하고, 초기 로딩 부하를 줄일 수 있습니다.

경로 기반 리소스 분리

input_diroutput_dir은 전역 경로 관리를 지원합니다. 리소스를 사용 시나리오에 따라 별도의 서브 디렉토리(예: LaunchAssets.xcassetsUIElements.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% 감소하는 효과를 보였습니다.

주요 모범 사례 체크리스트

  1. 설정 최적화
    • 접근 빈도에 따라 리소스 디렉토리 분할
    • filter를 사용하여 사용되지 않는 리소스 제외
    • 환경 변수를 활용한 경로 주입 (예: ${PROJECT_DIR})
  2. 템플릿 개발
    • 사용자 정의 템플릿 저장 경로: CustomTemplates/
    • 사전 로딩 메소드 명명 규칙: loadSynchronously() (동기) 및 loadAsynchronously() (비동기)
    • 필수 매개변수에 기본값 추가: {% set shouldPreload = param.preload | default(false) %}
  3. 코드 조직화
    • 핵심 리소스 열거형: Sources/Generated/CoreAssets.swift
    • 지연 로딩 모듈: Sources/Generated/FeatureAssets/

SwiftGen의 리소스 관리 기능은 타입 안전성 확보를 넘어섭니다. 위에 소개된 설정 최적화, 템플릿 커스터마이징, 그리고 코드 분할 전략을 통해 애플리케이션의 초기화 성능을 크게 향상시킬 수 있습니다. 프로젝트의 실제 리소스 분포를 고려하여, 앱 시작 시 핵심 경로에 위치한 리소스부터 사전 로딩을 적용하고 A/B 테스트를 통해 최적화 효과를 검증하는 것이 좋습니다.

태그: SwiftGen iOS 리소스 관리 코드 생성 성능 최적화

7월 20일 16:50에 게시됨