Burt.K

Awesome Discovery

[SE-0017] Unmanaged를 UnsafePointer로 변경하기

작성일 — 2025년 3월 9일

Table of Contents

UnmanagedUnsafePointer로 변경하기

소개

Swift 표준 라이브러리의 Unmanaged<Instance> 구조체는 ARC에 참여하지 않는 타입 안전 객체 래퍼를 제공한다. 이를 통해 사용자가 수동으로 retain/release 호출을 할 수 있다.

관련 자료:
Swift Evolution 토론,
리팩토링 제안 토론,
리뷰

동기

Unmanaged 타입으로 변환하거나 Unmanaged 타입에서 변환하기 위해 다음과 같은 메서드를 제공한다:

static func fromOpaque(value: COpaquePointer) -> Unmanaged<Instance>
func toOpaque() -> COpaquePointer

그러나 void *const void *를 받는 C API는 Swift에서 COpaquePointer 대신 UnsafePointer<Void>UnsafeMutablePointer<Void>로 노출된다. 실제로 사용자는 UnsafePointerCOpaquePointerUnmanaged로 변환해야 하며, 이로 인해 다음과 같은 복잡한 코드가 발생한다:

someFunction(context: UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque()))

info.retain = { Unmanaged<AnyObject>.fromOpaque(COpaquePointer($0)).retain() }
info.copyDescription = {
    Unmanaged.passRetained(CFCopyDescription(Unmanaged.fromOpaque(COpaquePointer($0)).takeUnretainedValue()))
}

제안하는 해결책

Unmanaged API에서 COpaquePointer 사용을 UnsafePointer<Void>UnsafeMutablePointer<Void>로 대체한다.

영향을 받는 함수는 fromOpaque()toOpaque()다. 현재 구현에서 매우 사소한 수정만 필요하다:

@_transparent
@warn_unused_result
public static func fromOpaque(value: UnsafePointer<Void>) -> Unmanaged {
    // 널 포인터 검사는 디버그 검사로, 특정 잘못된 포인터 값을 방어한다.
    _debugPrecondition(
      value != nil,
      "널 포인터에서 Unmanaged 인스턴스를 생성하려고 시도했습니다.")

    return Unmanaged(_private: unsafeBitCast(value, Instance.self))
}

@_transparent
@warn_unused_result
public func toOpaque() -> UnsafeMutablePointer<Void> {
    return unsafeBitCast(_value, UnsafeMutablePointer<Void>.self)
}

UnsafeMutablePointer 타입의 값은 UnsafePointerUnsafeMutablePointer를 받는 함수에 모두 전달할 수 있다. 따라서 간단하고 사용하기 쉽게 fromOpaque()의 입력 타입으로 UnsafePointer를, toOpaque()의 반환 타입으로 UnsafeMutablePointer를 선택한다.

위 예제 사용법은 더 이상 변환을 필요로 하지 않는다:

someFunction(context: Unmanaged.passUnretained(self).toOpaque())

info.retain = { Unmanaged<AnyObject>.fromOpaque($0).retain() }
info.copyDescription = {
    Unmanaged.passRetained(CFCopyDescription(Unmanaged.fromOpaque($0).takeUnretainedValue()))
}

기존 코드에 미치는 영향

이전에 COpaquePointer를 사용해 Unmanaged API를 호출하던 코드는 UnsafePointer를 사용하도록 변경해야 한다. COpaquePointer 버전은 전환을 돕기 위해 다음과 같은 가용성 속성을 유지할 수 있다:

@available(*, unavailable, message="fromOpaque(value: UnsafeMutablePointer<Void>)를 대신 사용하세요")
@available(*, unavailable, message="toOpaque() -> UnsafePointer<Void>를 대신 사용하세요")

COpaquePointer를 사용하는 코드는 이 타입에 크게 의존하지 않는 것으로 보이며, 이 변경으로 인해 큰 영향을 받지 않을 것이다.

고려한 대안들