옵셔널 이전 정리를 보면
optional은 열거형으로 이루어져 있으며
case가 some, none으로 이루어져 있습니다
일반 자료형보다 큰 범위의 자료형이라고 보면 됩니다
왜 Optional(3)으로 출력이 되는지 동작 과정에 대해서 알아보고자 합니다
우선 Optional은 case some / none으로 이루어져있는 코드에 대해서 알아보고자 합니다
optioanl type을 정의하고 있으며 복사 불가능한 타입(~Copyable)과 관련된 제한을 포함하고 있습니다
Optioanl type은 Wrapped라는 제네릭타입을 가지고 있습니다
어떤 타입이라도 래핑할 수 있습니다
그리고 컴파일러가 optioanl<Wrapped>를 최적화나 어떠한 처리를 할 수 있습니다
var int: Optional<Int> = .none //값이 없음
int = .some(42) //값이 존재
print(int) //Optional(42)
var optionalInt: Int? = nil //.none == nil
optionalInt = 42 //.some(42) 동일함
extension Optional: ExpressibleByNilLiteral where Wrapped: ~Copyable {
/// Creates an instance initialized with `nil`.
///
/// Do not call this initializer directly. It is used by the compiler when you
/// initialize an `Optional` instance with a `nil` literal. For example:
///
/// var i: Index? = nil
///
/// In this example, the assignment to the `i` variable calls this
/// initializer behind the scenes.
@_transparent
@_preInverseGenerics
public init(nilLiteral: ()) {
self = .none
}
}
//복사 불가능한 경우에만 적용되는 상황
//nil 리터럴을 사용해서 Optioanl을 none으로 초기화하는 메서드
optioanl정의문에서
none으로 초기화하는걸 볼 수 있습니다
public var unsafelyUnwrapped: Wrapped {
@inline(__always)
get {
if let x = self {
return x
}
_debugPreconditionFailure("unsafelyUnwrapped of nil optional")
}
}
}
그리고 언래핑을 사용하여
안정성 거사를 생략하고 강제로 추출하는것을 볼 수 있습니다
nil인 경우에 오류 발생하는것을 볼 수 있습니다
//복사 불가능할 때 적용
//개발자가 강제로 nil을 벗겨낼 때 발생하는 예외상황에 대해서 처리하는 문구도 볼 수 있습니다
extension Optional where Wrapped: ~Copyable {
// FIXME(NCG): Do we want this? It seems like we do. Make this public.
@_alwaysEmitIntoClient
public consuming func _consumingUnsafelyUnwrap() -> Wrapped {
switch consume self {
case .some(let x):
return x
case .none:
_debugPreconditionFailure("consumingUsafelyUnwrap of nil optional")
}
}
}
extension Optional {
/// - Returns: `unsafelyUnwrapped`.
///
/// This version is for internal stdlib use; it avoids any checking
/// overhead for users, even in Debug builds.
@inlinable
internal var _unsafelyUnwrappedUnchecked: Wrapped {
@inline(__always)
get {
//if let 바인딩을 통해서 안전성을 검사하고 값이 없을 경우에 오류 발생
if let x = self {
return x
}
_internalInvariantFailure("_unsafelyUnwrappedUnchecked of nil optional")
}
}
}
//복사 불가능 / if let 바인딩을 통하여 안전한지 검사하고 값이 존재하지 않는경우에 오류 발생
extension Optional where Wrapped: ~Copyable {
/// - Returns: `unsafelyUnwrapped`.
///
/// This version is for internal stdlib use; it avoids any checking
/// overhead for users, even in Debug builds.
@_alwaysEmitIntoClient
internal consuming func _consumingUncheckedUnwrapped() -> Wrapped {
if let x = self {
return x
}
_internalInvariantFailure("_uncheckedUnwrapped of nil optional")
}
}
이 코드들 같은 경우 각기 다른 변환 메서드를 보여주고 있습니다
그외에도 비교 메서드 등을 참고 할 수 있습니다
👇🏻👇🏻👇🏻👇🏻👇🏻
https://github.com/swiftlang/swift/blob/main/stdlib/public/core/Optional.swift#L121
본론으로 돌아와
왜 Optioanl을 프린트할 때 Optioanl로 보여지는가?
답은 CustomDebugStringConvertible 여기서 볼 수 있습니다
CustomDebugStringConvertible 프로토콜을 채택하여 확장을 통하여 Debug시에 Optional 타입일 때
"Optional(" + ')"을 출력하도록 지정하고 있습니다
개발자들에게 Optional 타입을 가시적으로 명확하게 알려줄 수 있는 역할을 하고 있는것을 볼 수 있습니다
여기서 CustomDebugStringConvertible 프로토콜을 추가적으로 알아보자면
읽기 전용 프로퍼티를 사용하여 디버그용 문자열을 반환하고있는걸 볼 수 있습니다
주로 객체 상태를 명확하게 알 수 있습니다
struct Apple: CustomDebugStringConvertible {
var name: String
var version: Float
var price: Int
var debugDescription: String {
return "Apple name \(name), version \(version), price \(price)
}
}
debugDescription을 반드시 채택하여 구체화 사용해야합니다
프로토콜을 읽기전용으로 사용하지만
채택했을 때 읽기 쓰기로 get/set으로 확장도 가능합니다
그리고 print도 보자면
여러가지 오버로딩으로 이루어진 함수입니다
//===--- Print.swift ------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#if !SWIFT_STDLIB_STATIC_PRINT
/// Writes the textual representations of the given items into the standard
/// output.
///
/// You can pass zero or more items to the `print(_:separator:terminator:)`
/// function. The textual representation for each item is the same as that
/// obtained by calling `String(describing: item)`. The following example
/// prints a string, a closed range of integers, and a group of floating-point
/// values to standard output:
///
/// print("One two three four five")
/// // Prints "One two three four five"
///
/// print(1...5)
/// // Prints "1...5"
///
/// print(1.0, 2.0, 3.0, 4.0, 5.0)
/// // Prints "1.0 2.0 3.0 4.0 5.0"
///
/// To print the items separated by something other than a space, pass a string
/// as `separator`.
///
/// print(1.0, 2.0, 3.0, 4.0, 5.0, separator: " ... ")
/// // Prints "1.0 ... 2.0 ... 3.0 ... 4.0 ... 5.0"
///
/// The output from each call to `print(_:separator:terminator:)` includes a
/// newline by default. To print the items without a trailing newline, pass an
/// empty string as `terminator`.
///
/// for n in 1...5 {
/// print(n, terminator: "")
/// }
/// // Prints "12345"
///
/// - Parameters:
/// - items: Zero or more items to print.
/// - separator: A string to print between each item. The default is a single
/// space (`" "`).
/// - terminator: The string to print after all items have been printed. The
/// default is a newline (`"\n"`).
public func print(
_ items: Any...,
separator: String = " ",
terminator: String = "\n"
) {
if let hook = _playgroundPrintHook {
var output = _TeeStream(left: "", right: _Stdout())
_print(items, separator: separator, terminator: terminator, to: &output)
hook(output.left)
}
else {
var output = _Stdout()
_print(items, separator: separator, terminator: terminator, to: &output)
}
}
/// Writes the textual representations of the given items most suitable for
/// debugging into the standard output.
///
/// You can pass zero or more items to the
/// `debugPrint(_:separator:terminator:)` function. The textual representation
/// for each item is the same as that obtained by calling
/// `String(reflecting: item)`. The following example prints the debugging
/// representation of a string, a closed range of integers, and a group of
/// floating-point values to standard output:
///
/// debugPrint("One two three four five")
/// // Prints "One two three four five"
///
/// debugPrint(1...5)
/// // Prints "ClosedRange(1...5)"
///
/// debugPrint(1.0, 2.0, 3.0, 4.0, 5.0)
/// // Prints "1.0 2.0 3.0 4.0 5.0"
///
/// To print the items separated by something other than a space, pass a string
/// as `separator`.
///
/// debugPrint(1.0, 2.0, 3.0, 4.0, 5.0, separator: " ... ")
/// // Prints "1.0 ... 2.0 ... 3.0 ... 4.0 ... 5.0"
///
/// The output from each call to `debugPrint(_:separator:terminator:)` includes
/// a newline by default. To print the items without a trailing newline, pass
/// an empty string as `terminator`.
///
/// for n in 1...5 {
/// debugPrint(n, terminator: "")
/// }
/// // Prints "12345"
///
/// - Parameters:
/// - items: Zero or more items to print.
/// - separator: A string to print between each item. The default is a single
/// space (`" "`).
/// - terminator: The string to print after all items have been printed. The
/// default is a newline (`"\n"`).
public func debugPrint(
_ items: Any...,
separator: String = " ",
terminator: String = "\n"
) {
if let hook = _playgroundPrintHook {
var output = _TeeStream(left: "", right: _Stdout())
_debugPrint(items, separator: separator, terminator: terminator, to: &output)
hook(output.left)
}
else {
var output = _Stdout()
_debugPrint(items, separator: separator, terminator: terminator, to: &output)
}
}
/// Writes the textual representations of the given items into the given output
/// stream.
///
/// You can pass zero or more items to the `print(_:separator:terminator:to:)`
/// function. The textual representation for each item is the same as that
/// obtained by calling `String(describing: item)`. The following example
/// prints a closed range of integers to a string:
///
/// var range = "My range: "
/// print(1...5, to: &range)
/// // range == "My range: 1...5\n"
///
/// To print the items separated by something other than a space, pass a string
/// as `separator`.
///
/// var separated = ""
/// print(1.0, 2.0, 3.0, 4.0, 5.0, separator: " ... ", to: &separated)
/// // separated == "1.0 ... 2.0 ... 3.0 ... 4.0 ... 5.0\n"
///
/// The output from each call to `print(_:separator:terminator:to:)` includes a
/// newline by default. To print the items without a trailing newline, pass an
/// empty string as `terminator`.
///
/// var numbers = ""
/// for n in 1...5 {
/// print(n, terminator: "", to: &numbers)
/// }
/// // numbers == "12345"
///
/// - Parameters:
/// - items: Zero or more items to print.
/// - separator: A string to print between each item. The default is a single
/// space (`" "`).
/// - terminator: The string to print after all items have been printed. The
/// default is a newline (`"\n"`).
/// - output: An output stream to receive the text representation of each
/// item.
public func print<Target: TextOutputStream>(
_ items: Any...,
separator: String = " ",
terminator: String = "\n",
to output: inout Target
) {
_print(items, separator: separator, terminator: terminator, to: &output)
}
/// Writes the textual representations of the given items most suitable for
/// debugging into the given output stream.
///
/// You can pass zero or more items to the
/// `debugPrint(_:separator:terminator:to:)` function. The textual
/// representation for each item is the same as that obtained by calling
/// `String(reflecting: item)`. The following example prints a closed range of
/// integers to a string:
///
/// var range = "My range: "
/// debugPrint(1...5, to: &range)
/// // range == "My range: ClosedRange(1...5)\n"
///
/// To print the items separated by something other than a space, pass a string
/// as `separator`.
///
/// var separated = ""
/// debugPrint(1.0, 2.0, 3.0, 4.0, 5.0, separator: " ... ", to: &separated)
/// // separated == "1.0 ... 2.0 ... 3.0 ... 4.0 ... 5.0\n"
///
/// The output from each call to `debugPrint(_:separator:terminator:to:)`
/// includes a newline by default. To print the items without a trailing
/// newline, pass an empty string as `terminator`.
///
/// var numbers = ""
/// for n in 1...5 {
/// debugPrint(n, terminator: "", to: &numbers)
/// }
/// // numbers == "12345"
///
/// - Parameters:
/// - items: Zero or more items to print.
/// - separator: A string to print between each item. The default is a single
/// space (`" "`).
/// - terminator: The string to print after all items have been printed. The
/// default is a newline (`"\n"`).
/// - output: An output stream to receive the text representation of each
/// item.
public func debugPrint<Target: TextOutputStream>(
_ items: Any...,
separator: String = " ",
terminator: String = "\n",
to output: inout Target
) {
_debugPrint(items, separator: separator, terminator: terminator, to: &output)
}
internal func _print<Target: TextOutputStream>(
_ items: [Any],
separator: String = " ",
terminator: String = "\n",
to output: inout Target
) {
var prefix = ""
output._lock()
defer { output._unlock() }
for item in items {
output.write(prefix)
_print_unlocked(item, &output)
prefix = separator
}
output.write(terminator)
}
internal func _debugPrint<Target: TextOutputStream>(
_ items: [Any],
separator: String = " ",
terminator: String = "\n",
to output: inout Target
) {
var prefix = ""
output._lock()
defer { output._unlock() }
for item in items {
output.write(prefix)
_debugPrint_unlocked(item, &output)
prefix = separator
}
output.write(terminator)
}
#endif
코드를 뭉치를 나눠서 설명하자면
public func print(
_ items: Any..., //가변 인자 출력할 객체들
separator: String = " ", //기본값 공백 ""은 공백 사라짐
terminator: String = "\n" //줄바꿈
)
//⭐️프린트 메서드와 유사함 하지만 디버깅용
public func debugPrint(
_ items: Any...,
separator: String = " ",
terminator: String = "\n"
)
//TextOutputStream protocol 각각 인자들을 문자열로 반환하여 출력함
internal func _print<Target: TextOutputStream>(
_ items: [Any],
separator: String = " ",
terminator: String = "\n",
to output: inout Target
)
internal func _debugPrint<Target: TextOutputStream>(
_ items: [Any],
separator: String = " ",
terminator: String = "\n",
to output: inout Target
)
결론적으로
Optioanl(value)가 출력되는 과정은 여러 단계를 거쳐 나오는걸 알 수 있습니다
단순히 print를 통해 나오는게 아니라
Optional의 extension에서 protocol- CustomDebugStringConvertible을 통해서
switch로 값의 유무를 판단하고 출력되는 과정을 볼 수 있었습니다
그리고 출력(print 함수)도 오버로딩 기능을 활용해서 구현된 것을 볼 수 있었습니다
Optioanl에 대해서 더 알고싶다면 (아래 세부 항목을 하나하나 클릭하면서 보시면 자세히 알 수 있습니다)
👇🏻👇🏻👇🏻👇🏻👇🏻
'Swift' 카테고리의 다른 글
Swift + RxSwift | UITableView/ UICollectionView 에서 작동하는 델리게이트 메서드 willDisplayCell (1) | 2024.12.11 |
---|---|
Using existentials and generics(익스텐셜 / 제네릭 타입) (0) | 2024.08.06 |
iOS/Siwft | XCode - SwiftLint 적용 (0) | 2024.06.25 |
Swift | 코드 스니펫 (코드 자동완성) (0) | 2024.06.04 |
Swift | guard let 뒤 변수를 어러개 넣을 수 있다고??!! (0) | 2024.06.04 |