Xcode에서 PCH부수기🔨

whitelips
a day of a programmer
8 min readFeb 26, 2024

$ rm AppName_Prefix.pch

Swift가 등장한지 오랜 시간이 지났습니다. 지금은 Swift를 사용하지 않는 iOS, macOS 프로젝트를 찾기가 더 어렵습니다. 모바일 앱 서비스도 이제 10년이 넘어가서 오래된 앱들이 존재합니다. 회사의 오래된 앱 서비스들은 Objective-C 를 사용하던 때에 만들어져있고, 대부분 AppName_Prefix.pch를 가지고 있을 것입니다.

PCH를 부수기에 앞서 PCH를 알아봅시다.

PCH란 무엇인가요?

PCH는 Precompiled Header의 약자입니다. PCH의 주요 역할은 다음과 같습니다.

  • 프로젝트 빌드 과정에서 자주 사용하는 헤더(.h) 파일들의 컴파일 결과를 미리 저장합니다.
  • 이를 통해 컴파일 시간 단축과 코드 중복 감소 효과를 얻을 수 있습니다.
  • Objective-C 시절에 PCH 파일은 중요한 역할과 효과를 가지고 있었습니다.

그러나 Swift 시대, 나아가서 SwiftUI를 써야하는 요즘에는 어떨까요?

PCH의 장점과 단점을 통해 더 알아보겠습니다.

장점

  • 효율적인 빌드 과정: 자주 변경되지 않는 헤더 파일을 PCH에 포함하여 매번 컴파일 하지 않아 빌드 시간을 줄여줍니다.
  • 개발 편의성: 공통 헤더 파일을 한 곳에 모아 관리합니다. (그래서 많은 앱들의 PCH 파일 이름이 AppName_Prefix.pch가 되었을거예요.)

단점

  • 숨겨진 의존성: 소스파일에서 보이지 않습니다. 따라서 필요한 모듈을 import 하지 않았는데 동작하는 코드가 될 수 있습니다.
  • 오버헤드 관리 필요: PCH 파일이 너무 커지면, 실제 필요하지 않은 코드도 모두 소스파일에 포함되어 컴파일 합니다. 메모리 사용량 증가와 컴파일 시간 증가 가능성이 있습니다.
  • 빌드 캐시: PCH 파일에 변경사항이 생길 때마다 PCH를 사용하는 모든 파일을 다시 컴파일합니다. 작은 변경사항인데 빌드 시간이 길어질 수 있습니다. 실제로 상수 하나 추가했 전체 빌드와 유사한 시간이 소요될 수도 있습니다.

실제 앱은 어떨까요?

자세히 밝힐 수 없으나 개발팀에서 사용하던 앱의 헤더 PCH는 아래와 같았습니다.

// 아래 코드는 회사 보안을 위해 감추고 설명을 위해 임시로 생성한 예시입니다.
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#endif

#define UNUSED_EQUAL_FUNCTION() (...)
#define UNUSED_GREATHER_THAN_FUNCTION() (...)

#if DEV
#define APP_API_URL @"dev-api.app.com"
#elif BETA
#define APP_API_URL @"beta-api.app.com"
#elif STAGE
#define APP_API_URL @"stage-api.app.com"
#else
#define APP_API_URL @"api.app.com"
#endif

#define FOO_STRANGE_URL "foo.app.com"

어떤가요? 내용을 본다면 모두 Swift 코드에서 처리할 수 있는 내용입니다. PCH 코드에 이렇게 기존의 상수들이 모여있다면 신규 상수를 추가할 때에 명확한 가이드와 요구사항이 없다면 계속 PCH 내용이 추가될 것입니다.

특히나 위 코드에서 APP_API_URL 과 FOO_STRANGE_URL 은 문자열 정의에 어떤 경우는 @ 가 있고 어떤 경우에는 없습니다. 실제 컴파일 오류가 나지 않으면 찾기 어려운 케이스입니다. Objective-C 에서는 필요했고, Swift 에서는 필요하지 않은 경우이기도 합니다.

PCH를 부수면 얻을 수 있는 효과

그렇다면 PCH를 제거하면, 즉 Swift로 변환하면 어떤 효과가 있을까요?

  • 모듈화와 재사용성에 도움이 됩니다. 필요한 소스코드에서 필요한 만큼만 정의하고 사용하게 됩니다. 분리한 내용이 필요하면 해당 모듈을 참조하여 사용합니다.
  • 가독성과 유지보수성이 좋아집니다. PCH는 전역 헤더 파일이기 때문에 각 코드에서 어떤 헤더가 참조가 되었는지 모르는 경우가 생길 수 있습니다. 이제 더 이상 C스타일의 코드를 보지 않아도 됩니다.
  • 컴파일 시간의 최적화. WWDC에서 매번 애플이 발표하는 Xcode 또는 Swift 빌드 시간이 빨라졌어요는 Swift에만 해당하는 이야기입니다. PCH의 컴파일 시간 단축 효과도 있겠지만 이제 Swift 의 영향 범위 아래로 옮겨볼 수 있습니다.
  • 더 많은 Swift. 스위프트다운 코드를 더 작성할 수 있습니다. PCH에서 사용하지 못했던 Swift 의 새로운 문법과 표현이요.
  • 플랫폼 호환성. 오래된 헤더 파일에서 혹시 iOS, macOS, watchOS 등의 구분을 보았나요? Swift로 작성하면 이러한 분기 제거 가능성이 더 커지고 호환성이 좋아집니다.

PCH 부수기

PCH 부수기를 바로 진행하면 컴파일 에러가 발생합니다. 따라서 단계별로 진행할 수 있습니다.

  1. PCH를 대체할 Swift 파일 만들기
  2. 안쓰는 PCH 내용 삭제하기(오래 관리되지 않아 존재하는 경우에만)
  3. #define 상수를 Swift 상수로 변경하기
  4. 그러나 코드 변경은 적게하기

여기서 코드 변경은 적게하는 방법을 조금 더 자세히 이야기 해보겠습니다. 위 예시로 들었던 헤더 파일에서 상수들은 APP_API_URL 과 같이 Swift 컨벤션을 따르고 있지 않습니다. 이를 한번에 모두 교체할 수 있지만 이렇게 되면 코드 변경이 많아지고 코드 리뷰어가 좋아하지 않겠죠? 최악의 경우 내가 작성한 코드 변경사항의 리뷰는 최하위 우선순위로 보게될지도 모릅니다.

따라서 사용하는 Swift 컨벤션 도구에 따라 다르지만 SwiftLint를 사용한다면 PCH대체로 만든 소스파일에서 대체 상수를 작성하는 라인에 // swiftlint:disable 을 추가해주세요. 위반하는 룰만 피할 수도 있고 간단하게 대체 상수 정의 라인 앞에서 // swiftlint:disable all 을 추가하여 모두 비활성화도 가능합니다.

이제 결과를 봅시다.

* 저는 여기어때와 관련이 없습니다. *

PCH 부수기 빌드 결과를 확인해봅니다.

Derived 경로에서 하위 경로중 PrecompiledHeaders를 살펴보았습니다. 제가 일하는 앱은 PCH 개선 전에 740KB 파일 크기를 가지고 있었으나 PCH 부수기 후에는 723KB 파일 크기를 가지고 있습니다. 백라인도 안되는 헤더파일이었지만 그래도 2~3% 정도의 효과가 있었습니다.

다음은 빌드시간을 확인합니다.

  • 개선전: 307.8초, 272.8초, 287.6초 => 3회 평균 289.4초
  • 개선후: 286.9초, 271.3초, 281.3초 => 3회 평균 279.8초

결과를 보니 약간이지만 빌드시간이 줄었습니다. 왜그럴까요? PCH의 컴파일 단축 시간은 Objective-C에서나 유효한 것이고 Swift 프로젝트에서는 오히려 방해요소로 작용할 수 있는 것이죠.

다음은 뭘까요?

PCH를 부수기를 하면 끝일까요? 다음 단계는 또 다른 레거시 헤더파일들을 제거 하는 것입니다. 같은 경험을 반복하는 것이라 어렵지 않을 텐데요. 다만 Objective-c 코드가 남아있다면 @objc annotation을 활용해서 해결할 수 있습니다.

중요한 것은 빌드 타임의 상수를 Swift 상수로 변경하고 끝내지 마세요. 이러한 코드에서 상수 결정을 런타임으로 가져올 수 있습니다.

  • 빌드 타임 상수를 줄이면 여러 빌드 스킴을 하나로 통합할 수 있습니다.
  • 하나의 테스트 앱으로 여러 테스트 환경을 런타임 변경을 지원하기 쉽습니다.
  • 하나의 빌드 스킴도 CI에서 빌드 플래그를 추가하도록 하면 이전의 빌드 스킴에 따른 별도 배포 환경을 사용할 수 있습니다.

여기에 숨겨진 효과가 더 있습니다.

도구의 힘을 빌리기 쉬워요

C매크로로 된 빌드타임 상수들은 각 빌드 환경을 빌드해야 실제 빌드 에러를 확인할 수 있습니다. 그러나 런타임 상수로 변경된 혹은 스위프트로 가져온 상수들은 모두 빌드하지 않고도 Xcode IDE에서 인덱싱이 잘되어 에러 또는 경고를 표시해줄 수 있습니다.

보통 검수 단계에서 배포 빌드를 진행하게 되어 실제 앱스토어까지 빌드 분기에 따른 사이드이펙트가 반영되지는 않을 것입니다. 그래도 검수 준비 또는 배포 준비까지 가지 않고 개발 단계에서 빌드 에러를 미리 확인할 수 있다면 편리하겠지요.

마지막

무한도전에서 나온 유명한 말입니다.

“늦었다고 생각할 대가 진짜 너무 늦었다. 그러니 지금 당장 시작해라"

아직도 Xcode 프로젝트에서 오래된 C헤더를 가지고 있나요? 이제는 더 미루지말고 지워보아요.

--

--

whitelips
a day of a programmer

Software Engineer with 10+ years in iOS, focusing on performance optimization, modularization, and innovative solutions. Proven leader in major tech projects.