Optional이란 무엇인지 설명하시오.(2)

som·2023년 12월 12일
0

SwiftAnatomy

목록 보기
1/9

빠르게 돌아온(?) Optional, 두 번째 시간입니다!
공부할수록 안정성에 대한 아주 매력적인 문법이라는 걸 깨닫고 있습니다😊

혹시 1편을 안 보고 오신 분들은 요기를 클릭해서 보고 오시면 됩니다!

그러면 더 파헤치러 가볼까요?

개념

unsafelyUnwrapped

@inlinable
  public var unsafelyUnwrapped: Wrapped {
    @inline(__always)
    get {
      if let x = self {
        return x
      }
      _debugPreconditionFailure("unsafelyUnwrapped of nil optional")
    }
  }

해당 코드는 Apple > swift > public > ... > Optional.swift에서 볼 수 있습니다.

인스턴스가 'nil'인지 여부를 확인하지 않고 래핑 해제된 값입니다. unsafelyUnwrapped 프로퍼티는 강제 unwrap 연산자(후위 !)와 동일한 값을 제공합니다. 그러나 최적화된 빌드(-O)에서는 현재 인스턴스에 실제로 값이 있는지 확인하기 위한 검사가 수행되지 않습니다. 'nil' 값의 경우 이 프로퍼티에 접근하는 것은 심각한 프로그래밍 오류이며 정의되지 않은 동작이나 런타임 오류로 이어질 수 있습니다.

디버그 빌드(-Onone)에서 unsafelyUnwrapped 프로퍼티는 접미사 ! 연산자를 사용하는 것과 동일한 동작을 가지며 인스턴스가 nil인 경우 런타임 오류를 트리거합니다.

unsafelyUnwrapped 프로퍼티가 더 제한적이므로 unsafeBitCast(_:) 함수를 호출하는 것보다 권장됩니다. 프로퍼티에 접근하면 디버그 빌드에서 확인이 가능하기 때문입니다.

경고: 이 프로퍼티는 성능을 위해 안전이 보장되지 않습니다. 이 인스턴스가 결코 nil과 같지 않을 것이라고 확신하는 경우에만 그리고 후위 ! 연산자를 사용해 본 후에만 unsafelyUnwrapped를 사용하십시오.

오호! 결국에는 이것도 강제언래핑인 !와 쓰임이 같은 거 같습니다.
코드 내부를 보면 if let문을 사용해서 값이 있다면 값을 리턴하지만 없는 경우에는 _debugPreconditionFailure()이 실행되는 거 같습니다.

@inlinable

이 특성을 함수, 메서드, 계산된 속성, 첨자, 편의 초기화 또는 초기화 해제 선언에 적용하여 해당 선언의 구현을 모듈 공개 인터페이스의 일부로 노출합니다. 컴파일러는 인라인 가능한 기호에 대한 호출을 호출 사이트의 기호 구현 복사본으로 대체할 수 있습니다.

인라인 가능 코드는 모든 모듈에 선언된 공용 기호와 상호 작용할 수 있으며, usableFromInline 프로퍼티로 표시된 동일한 모듈에 선언된 내부 기호와 상호 작용할 수 있습니다. 인라인 가능한 코드는 private 또는 fileprivate 기호와 상호 작용할 수 없습니다.

_debugPreconditionFailure()

@usableFromInline @_transparent
internal func _debugPreconditionFailure(
  _ message: StaticString = StaticString(),
  file: StaticString = #file, line: UInt = #line
) -> Never {
#if SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE
  _preconditionFailure(message, file: file, line: line)
#else
  if _slowPath(_isDebugAssertConfiguration()) {
    _precondition(false, message, file: file, line: line)
  }
  _conditionallyUnreachable()
#endif
}

...워워.. 처음 보는 형태의 코드가 나오는 군요?

디버그 라이브러리 전제 조건 확인은 디버그 모드에서만 활성화됩니다. 릴리스 및 빠른 모드에서는 비활성화됩니다. 디버그 모드에서는 오류 메시지를 인쇄하고 중단합니다. 이는 검사가 가능한 모든 오류를 포괄적으로 검사하지 않을 때 사용하도록 고안되었습니다.

아~ 디버그 모드에서만 사용되는 오류 처리 방법인 거 같습니다만... 여기서 끝내면 너무 이해가 덜 된 상태에서 끝나는 거 같아 Apple 공식문서를 뒤져보았습니다!

이 특성을 함수, 메서드, 계산된 속성, 첨자, 초기화 또는 초기화 해제 선언에 적용하여 선언과 동일한 모듈에 정의된 인라인 가능 코드에서 해당 기호를 사용할 수 있도록 합니다. 선언에는 내부 액세스 수준 수정자가 있어야 합니다. usableFromInline으로 표시된 구조나 클래스는 해당 속성에 public 또는 usableFromInline인 타입만 사용할 수 있습니다. usableFromInline으로 표시된 열거형은 해당 사례의 원시 값과 관련 값에 대해 public 또는 usableFromInline인 타입만 사용할 수 있습니다.

간단히 말하면, usableFromInline는 같은 접근 제어자끼리만 사용이 가능하다는 뜻입니다. 또다른 접근 제어자의 느낌이라 신선하네요😳

의미상 @_transparent는 "이 작업을 기본 작업인 것처럼 처리"와 같은 것을 의미합니다. 이 이름은 컴파일러와 컴파일된 프로그램 모두 작업에서 구현까지 "투시"한다는 의미입니다.

이는 여러 가지 결과를 가져옵니다.

  • @_transparent로 표시된 함수에 대한 모든 호출은 -Onone에서도 데이터 흐름 관련 진단을 수행하기 전에 인라인되어야 합니다. 이는 데이터 흐름 오류를 포착하는 데 필요할 수 있습니다.
  • 이 때문에 @_transparent 함수는 암시적으로 인라인 가능합니다. 즉, 구현을 변경해도 기존 컴파일된 바이너리의 호출자에게 영향을 주지 않을 가능성이 높습니다.
  • 이 때문에 public 또는 @usableFromInline @_transparent 함수는 공개 기호만 참조해야 하며 해당 함수가 포함된 모듈에 대한 지식을 기반으로 최적화되어서는 안 됩니다. [전자는 Sema의 검사에 의해 포착됩니다.]
  • 디버그 정보는 호출 함수를 한 단계씩 실행할 때 인라인 작업을 건너뛰어야 합니다(SHOULD).

ㅇ0ㅇ... 제가 대충 이해했을 때, Apple 측에서 swift 코드를 만들 때의 용도로 사용되는 거 같습니다. @_transparent이 명시된 함수는 수정이 되어서는 안 된다는 제약이 있고, 구현 시 비공개 항목(진정 비공개 함수 또는 다음 릴리스에서 사라질 수 있는 내부 함수)이 사용되는 것도 권장하지 않습니다. 즉, public 또는 @usableFromInline된 코드만 권장하고 있습니다.

func preconditionFailure(
    _ message: @autoclosure () -> String = String(),
    file: StaticString = #file,
    line: UInt = #line
) -> Never

오~ 형태가 앞의 코드와 비슷하고 같은 문서에 있는 코드라 열심히 읽어보았는데요!

message
플레이그라운드 또는 -Onone 빌드에서 인쇄할 문자열입니다. 기본값은 빈 문자열입니다.

file
메시지와 함께 인쇄할 파일 이름입니다. 기본값은 preconditionFailure(_:file:line:)이 호출되는 파일입니다.

line
메시지와 함께 인쇄할 줄 번호입니다. 기본값은 preconditionFailure(_:file:line:)이 호출되는 줄 번호입니다.

API가 부적절하게 사용되었고 실행 흐름이 호출에 도달할 것으로 예상되지 않는 경우에만 제어 흐름이 호출에 도달할 수 있는 경우 이 함수를 사용하여 프로그램을 중지합니다. 예를 들어, 다른 경우 중 하나가 충족되어야 한다는 것을 알고 있는 스위치의 default 경우입니다.

이 함수의 효과는 사용된 빌드 플래그에 따라 달라집니다.

  • 플레이그라운드 및 -Onone 빌드(Xcode 디버그 구성의 기본값)에서는 메시지를 인쇄한 후 디버그 가능한 상태에서 프로그램 실행을 중지합니다.
  • -O 빌드(Xcode 릴리스 구성의 기본값)에서는 프로그램 실행을 중지합니다.
  • -Ounchecked 빌드에서 최적화 프로그램은 이 함수가 호출되지 않는다고 가정할 수 있습니다. 해당 가정을 충족하지 못하는 것은 심각한 프로그래밍 오류입니다.

공부하는 내내 -Onone 빌드가 무엇인지 궁금했는데, Xcode 디버그 구성의 기본값이라고 하네요!
정리해보자면, preconditionFailure(_:file:line:) 함수가 실행되면 프로그램 실행이 중지된다고 합니다.

@inlinable
  public var unsafelyUnwrapped: Wrapped {
    @inline(__always)
    get {
      if let x = self {
        return x
      }
      _debugPreconditionFailure("unsafelyUnwrapped of nil optional")
    }
  }

해당 코드를 다시 보면, "unsafelyUnwrapped of nil optional"이라는 메세지를 -Onone 빌드에서 출력하고 프로그램 실행이 중지된다는 걸 알 수 있네요!

??

옵셔널 병합 연산자에서 많이 보던 함수죠!

nil 병합 작업을 수행하여 래핑된 값을 반환합니다. Optional 인스턴스 또는 기본값입니다. nil 병합 작업은 값이 있는 경우 왼쪽을 풀거나 오른쪽을 기본값으로 반환합니다. 이 작업의 결과는 왼쪽의 Wrapped 타입의 비선택적 타입을 갖게 됩니다.

@_transparent
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
    rethrows -> T {
  switch optional {
  case .some(let value):
    return value
  case .none:
    return try defaultValue()
  }
}

autoclosure는 함수에 인수로 전달되는 표현식을 래핑하기 위해 자동으로 생성되는 클로저입니다. 어떤 인수도 취하지 않으며, 호출되면 그 안에 포함된 표현식의 값을 반환합니다. 이러한 구문상의 편리함을 통해 명시적 클로저 대신 일반 표현식을 작성하여 함수 매개변수 주위의 중괄호를 생략할 수 있습니다.

let notSoGoodNumber = Int("invalid-input") ?? Int("42")

이렇게 간결하게 표현할 수 있는 방법이죠!

이 함수를 정리해보자면, nil일 경우에는 defaultValue로 받은 클로저를 반환하고, nil이 아닐 경우에는 옵셔널에 감싸져 있던 값을 반환하는 함수네요!

간단정리

Optional의 내부코드는 제가 쓴 부분보다 훨씬 방대하지만... 다하면 너무 길어질 거 같아서 옵셔널 강제 추출과 바인딩 부분만 살짝 정리를 해보았습니다.

!를 사용하는 강제언래핑, guard let 구문 등, 옵셔널 바인딩을 통해 옵셔널 내부의 값을 가져올 수 있다.

profile
얼레벌레 취준 공부 중인 초보 개발자

0개의 댓글