개발을 진행하면서 (개인적으로) 매우 흥미로운 것을 발견하였습니다.
@IBAction func textFieldTExtChanged(_ sender: UITextField) {
print("sender -> ", sender.text)
// 만약 숫자만 포함
if isNumber(sender.text) {
// Q. 왜 옵셔널인데 sender.text만 해도 옵셔널이 해제되는거지?
// 문자열 보간법으로 작성하면 Optional로 나타남!!
resultLabel.text = "\\(sender.text)"
// 하지만 text에 바로 할당하면 Optional이 해제됨!!
resultLabel.text = sender.text
} else { resultLabel.text = "숫자가 아닙니다" }
}
sender.text는 String?인데, resultLabel.text = sender.text로 작성하면 Optional이 사라진 상태로 표현되지만, resultLabel.text = "\(sender.text)" 형식의 문자열 보간법으로 작성하면 Optional(””)의 형태로 표현되는 현상을 발견하였습니다.
원인을 파악하기 위해 공식문서에 나타나있는 Optional.swift에 대해 찾아보았습니다. Swift 언어는 오픈소스로 공개되어 있기 때문에 코드를 확인할 수 있습니다.
https://github.com/swiftlang/swift/blob/main/stdlib/public/core/Optional.swift
swift/stdlib/public/core/Optional.swift at main · swiftlang/swift
The Swift Programming Language. Contribute to swiftlang/swift development by creating an account on GitHub.
github.com
@frozen public enum Optional<Wrapped> : ~Copyable where Wrapped : ~Copyable {
/// The absence of a value.
///
/// In code, the absence of a value is typically written using the `nil`
/// literal rather than the explicit `.none` enumeration case.
case none
/// The presence of a value, stored as `Wrapped`.
case some(Wrapped)
...
}
옵셔널은 enum 타입으로, case가 none일때와 some(Wrapped), 값이 있는 상태일 때를 구분하여 개발되었습니다.
문서를 읽다보면 옵셔널이 어떻게 출력되는지 확인할 수 있었습니다.
@_unavailableInEmbedded
extension Optional: CustomDebugStringConvertible {
/// A textual representation of this instance, suitable for debugging.
public var debugDescription: String {
switch self {
case .some(let value):
#if !SWIFT_STDLIB_STATIC_PRINT
var result = "Optional("
#if !$Embedded
debugPrint(value, terminator: "", to: &result)
#else
_ = value
"(cannot print value in embedded Swift)".write(to: &result)
#endif
result += ")"
return result
#else
return "(optional printing not available)"
#endif
case .none:
return "nil"
}
}
}
옵셔널은 CustomDebugStringConvertible 을 준수하는데, 만약 없다면 “nil”을 반환하고, 값이 있다면 “Optional(값)”으로 반환되도록 설계되었습니다.
#if !SWIFT_STDLIB_STATIC_PRINT 에서 #는 전처리기 지시어로, 컴파일 이전에 미리 처리되는 문장입니다. 해당 구문에 대해 이해가 되지 않아 Print.swift 구문에서 이와 관련된 공식문서를 확인해보았습니다.
https://github.com/swiftlang/swift/blob/main/stdlib/public/core/Print.swift
swift/stdlib/public/core/Print.swift at main · swiftlang/swift
The Swift Programming Language. Contribute to swiftlang/swift development by creating an account on GitHub.
github.com
#if !SWIFT_STDLIB_STATIC_PRINT
/// Writes the textual representations of the given items into the standard
/// output.
...
Print문의 가장 첫 시작이 위에서 본 문구로 시작함을 알 수 있습니다. standard output으로 반환해줄 수 있는 경우에 Print문이 실행된다는 의미를 가진다고 생각하였습니다.
즉, 옵셔널에서 확인했던 코드는 stdout으로 반환될 수 있을 때(print문으로 확인할 수 있을 때), “Optional()”이라는 문자열이 반환되는 것을 의미하는 코드입니다. 만약 stdout으로 반환할 수 없을 때에는(print를 확인할 수 없는 로우 레벨 영역의 무엇인가가 있는 것 같습니다.) #else 구문을 통해 "(optional printing not available)"이 반환됩니다.
Optional이 출력되는 매커니즘에 대해 알아보았으니, 이제 문자열 보간법과 UILabel.text의 코드를 확인하면 원인을 파악할 수 있을 것이라 생각하였습니다. StringInterPolation.swift에서 확인하였습니다.
문자열 보간법(StringInterpolation)
https://github.com/swiftlang/swift/blob/main/stdlib/public/core/StringInterpolation.swift
swift/stdlib/public/core/StringInterpolation.swift at main · swiftlang/swift
The Swift Programming Language. Contribute to swiftlang/swift development by creating an account on GitHub.
github.com
@inlinable
public mutating func appendInterpolation<T>(_ value: T)
where T: CustomStringConvertible
{
value.description.write(to: &self)
}
문자열 보간법은 addInterpoltaion()을 통해 value를 write합니다. 즉, 자신이 받은 값(여기서 자신은 문자열 보간을 의미합니다.)을 그저 write하고, 이를 반환하는 것입니다.
따라서 Optional 타입을 문자열 보간법으로 표현하면 “Optional(값)”이 그대로 write되어 이를 반환하는 것입니다. 문자열 보간법은 받은 value를 그저 write하는데, Optional을 통해 받은 value가 “Optional()”의 형식이기 때문입니다.
UILabel.text
그 다음으론 UILabel.text는 옵셔널이 해제된 상태로 출력되는지 확인하고자 하였습니다. 하지만 UIKit은 오픈 소스가 아니기 때문에 이와 관련하여 코드를 확인할 수 없었습니다. (혹시 오픈 소스로 공개되어 있다면 알려주시면 감사하겠습니다.)
하지만 다르게 출력이 되는 것을 보아하니 문자열 보간법과는 다른 방식으로 구현되어 있는 것 같습니다. 예를 들어 text 자체적으로 nil이 아니라면 Optional을 해제하도록 구성되어 있을 수 있을 것 같습니다.
이번 학습을 통해 옵셔널과 문자열 보간법의 동작 원리에 대해 파악할 수 있었습니다. 또한 Swift 언어 자체는 오픈 소스이지만 UIKit의 프레임워크는 공개되어 있지 않는 것을 알게되었고, 프레임워크 내부의 컴포넌트가 타입이 같아도 출력 결과가 다르게 나올 수 있겠구나를 알 수 있었습니다.
'iOS' 카테고리의 다른 글
[iOS] Xcode의 빌드 과정 (0) | 2025.01.16 |
---|---|
[iOS] SwiftLint 설치 및 적용하기 (0) | 2023.12.23 |
[iOS] 협업 시 하나의 Bundle Identifier로 설정하기 (Provisioning Profile, Certificate 공유) (0) | 2023.11.21 |
[iOS] Firebase FCM을 이용한 서버 푸시 구현 (4) | 2023.11.15 |
[iOS] UserNotification을 사용하여 로컬 알림 만들기 (0) | 2023.10.03 |