Get Up & Code, MacKin Talk

[iOS] 자주 사용하는 컴파일 조건구문에 대하여 (#if, #endif, canImport 등) + available, unavailable 본문

IOS

[iOS] 자주 사용하는 컴파일 조건구문에 대하여 (#if, #endif, canImport 등) + available, unavailable

맥킨 2023. 10. 10. 23:08

iOS를 개발하면서, 타겟 버전이나 플랫폼 조건 등에서 #if, #endif 등 컴파일 조건 구문을 익숙하게 봐오셨을 거라 생각합니다.

 

사용법을 보기 전에 #if로 시작하고, #endif로 끝나는 구문이 어떤 건지 자세히 알아보면 좋을 것 같습니다.

 

이렇게 컴파일 단계에서 조건을 추가하기 위해 사용되는 구문을 Compiler Control Statement라고 부릅니다.

 

Compiler Control Statement의 역할은 타겟이 컴파일되는 과정에서 컴파일러의 작동 양상을 바꿀 수 있도록 해줍니다.
크게 보자면 총 3가지로 나눌 수 있고, 각각은 하단과 같습니다. (물론 각각에 대해서도 플랫폼, 버전 등에 따라 많은 조건들이 있네요.)

 

1. conditional-compilation-block

2. line-control-statement

3. diagnostic-statement

 

각각의 요소에 대해서 좀더 자세히 알아보겠습니다.

 

1. conditional-compilation-block

컴파일러에 주어진 조건에 따라 조건적으로 컴파일을 할 수 있도록 해주는 구문입니다.

모든 조건 컴파일 블럭은 #if compilation directive로 시작하고, #endif compilation directive로 끝납니다. (compilation directive: 컴파일 지시어)

 

#if <#compilation condition#>
<#statements#>

#endif

일반적인 if문과 다르게 compilation 조건문의 경우, 컴파일 타임에 여부가 결정됩니다.

따라서 컴파일 타임에 compilation condition이 참인 경우에만 컴파일과 실행이 이뤄지게 됩니다.

compilation condition은 true, false의 불리언 값 리터럴을 포함하고, -D 커맨드 라인 플래그 또는 하단 표에 표기된 플랫폼 조건들을 함께 활용할 수 있습니다.

swift(), compiler() 조건의 경우, major number와 닷(.)을 활용해 선택적으로 minor number, patch number 등을 조건으로 사용할 수 있습니다.

추가로 비교문과 버전 넘버 사이에는 공백을 허용하지 않습니다.

#if compiler(>=5)
print("Compiled with the Swift 5 compiler or later")
#endif

#if swift(>=4.2)
print("Compiled in Swift 4.2 mode or later")
#endif

#if compiler(>=5) && swift(<5)
print("Compiled with the Swift 5 compiler or later in a Swift mode earlier than 5")

(예시 코드에서도 비교문 사이에 공백을 사용하지 않았음.)

canImport()의 경우, dot(.)을 활용해서 SubModule에 대한 표기가 가능합니다.

#if canImport(MyModule.SubModule)
import MyModule.SubModule // MyModule.SubModule을 가져올 수 있는 경우 실행할 코드
#else // MyModule.SubModule을 가져올 수 없는 경우 실행할 코드
#endif

targetEnvironment()의 경우, 지정된 특정 환경으로 컴파일할 경우만 true로 설정되고, 그 이외는 false로 처리됩니다.

논리 연산자인 &&, ||, and ! 사용이 가능하고, 그룹 짓기 위한 용도로 소괄호의 사용도 가능합니다.

또한 일반적인 if 문과 동일하게 연쇄적 조건 분기가 가능합니다.

#if <#compilation condition 1#>
<#statements to compile if compilation condition 1 is true#>
#elseif <#compilation condition 2#>
<#statements to compile if compilation condition 2 is true#>
#else
<#statements to compile if both compilation conditions are false#>

swift() 또는 compiler() 조건문이 들어간 경우를 제외하고, 컴파일이 되지 않더라도 파서는 모든 컴파일 블럭을 파싱합니다.

즉, 모든 구문은 언어와 컴파일러 버전이 플랫폼 조건에 매칭된 경우에만 파싱됩니다.

이러한 예외 처리는 예전 버전의 컴파일러가 새 버전의 swift 문법에 대한 파싱 시도를 막아줍니다.

위에서 다룬 compilation-condition을 정리하면 하단처럼 나타낼 수 있습니다.

if-directive-clause → if-directive compilation-condition statements?
elseif-directive-clauses → elseif-directive-clause elseif-directive-clauses?
elseif-directive-clause → elseif-directive compilation-condition statements?
else-directive-clause → else-directive statements?

if-directive → #if
elseif-directive → #elseif
else-directive → #else
endif-directive → #endif

compilation-condition → platform-condition
compilation-condition → identifier
compilation-condition → boolean-literal
compilation-condition → ( compilation-condition )
compilation-condition → ! compilation-condition
compilation-condition → compilation-condition && compilation-condition
compilation-condition → compilation-condition || compilation-condition

platform-condition → os ( operating-system )
platform-condition → arch ( architecture )
platform-condition → swift ( >= swift-version ) | swift ( < swift-version )
platform-condition → compiler ( >= swift-version ) | compiler ( < swift-version )
platform-condition → canImport ( import-path )
platform-condition → targetEnvironment ( environment )

operating-system → macOS | iOS | watchOS | tvOS | Linux | Windows

architecture → i386 | x86_64 | arm | arm64

swift-version → decimal-digits swift-version-continuation?

swift-version-continuation → . decimal-digits swift-version-continuation?

environment → simulator | macCatalyst

 

2. Line Control Statement

 

컴파일되는 과정에서 달라질 수 있는 코드의 라인 정보와 파일 이름을 구체화하기 위해 사용합니다.

라인 컨트롤 구문을 사용하여 진단 및 디버깅 목적으로 Swift에서 사용하는 소스 코드 위치를 변경합니다.

형식은 다음과 같습니다.

#sourceLocation(file: <#file path#>, line: <#line number#>)
#sourceLocation()

첫 번째 형식은 #line, #file, #fileID 및 #filePath 리터럴 식의 값을 변경합니다.

줄 번호는 #line의 값을 변경하며 0보다 큰 정수 리터럴입니다.

파일 경로는 #file, #fileID 및 #filePath의 값을 변경하며 문자열 리터럴입니다. 지정된 문자열은 #filePath의 값이 되고

문자열의 마지막 경로 구성 요소는 #fileID의 값에 사용됩니다. 파일, #파일ID 및 #파일 경로에 대한 자세한 내용은 애플 공식 문서 리터럴 표현식(https://docs.swift.org/swift-book/documentation/the-swift-programming-language/expressions#Literal-Expression)에서 찾아볼 수 있습니다.

줄 제어 문의 두 번째 형태인 #sourceLocation()은 소스 코드 위치를 기본 줄 번호와 파일 경로로 재설정합니다.

 

 

3. Diagnostic Statement

 

좀더 자세하게 묘사하면 Compile-Time Diagnostic Statement라고 할 수 있습니다.

Swift 5.9 버전보다 상위 버전에서는 #warning과 #error 구문을 사용할 수 있습니다. (컴파일 과정에서 진단 결과를 반환합니다.)

[warning(_:)](http://developer.apple.com/documentation/swift/documentation/swift/error(_:)

[error(_:)](http://developer.apple.com/documentation/swift/documentation/swift/error(_:)

 

 

 

추가로 위에서 다룬 Compiler Control Statement는 아니지만,

코드에서 많이 사용하는 Available Condition에 대해서 간단히 소개하겠습니다.

 

형식은 다음과 같습니다.

if #available(<#platform name#> <#version#>, <#...#>, *) {
<#statements to execute if the APIs are available#>
} else {
<#fallback statements to execute if the APIs are unavailable#>
}

사용하려고 하는 API가 런타임에 사용가능한지 여부에 따라 조건적으로 코드 블록을 실행합니다.

컴파일러는 해당 코드 블록의 API를 사용할 수 있는지 확인할 때 사용 가능성 조건의 정보를 사용합니다.

 

Boolean 조건문처럼 논리연산자(&& , ||)의 사용은 불가합니다.

또한 사용이 불가능한 상황을 위해 #available에 ! 연산자를 추가하여 사용하지 않습니다.

대신

if #unavailable(<#platform name#> <#version#>, <#...#>) {
<#fallback statements to execute if the APIs are unavailable#>
} else {
<#statements to execute if the APIs are available#>
}

위와 같이 #unavailable을 사용할 수 있습니다.

*argumen가 암시적이고, 꼭 포함되지 않아도 됩니다.

#available이 있음에도 #unavailable이 따로 있는데, 공식문서에서는 syntactic sugar이라고 표현하네요.

위키디피아 표현에 의하면 '읽거나, 표현하기에 좀 더 쉽도록 하기 위해 고안된 프로그래밍 언어 문법'이라고 합니다.
[ref](https://en.wikipedia.org/wiki/Syntactic_sugar)

내용을 정리하면 하단처럼 같이 표기할 수 있습니다.

availability-condition → #available ( availability-arguments )
availability-condition → #unavailable ( availability-arguments )
availability-arguments → availability-argument | availability-argument , availability-arguments
availability-argument → platform-name platform-version
availability-argument → *

platform-name → iOS | iOSApplicationExtension
platform-name → macOS | macOSApplicationExtension
platform-name → macCatalyst | macCatalystApplicationExtension
platform-name → watchOS | watchOSApplicationExtension
platform-name → tvOS | tvOSApplicationExtension
platform-name → visionOS
platform-version → decimal-digits
platform-version → decimal-digits . decimal-digits
platform-version → decimal-digits . decimal-digits . decimal-digits

ref

---

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Availability-Condition

 

Documentation

 

docs.swift.org

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Compiler-Control-Statements

 

Documentation

 

docs.swift.org

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Compiler-Control-Statements

 

Documentation

 

docs.swift.org