Pointers in swift

C-API 사용하기

Objective-C와의 호환 기능 덕분에 Swift에서는 C 언어의 타입이나 기능들을 사용할 수 있게 되었다.

원시타입들

int, bool, short, char… 등의 C 원시타입은 CInt, CBool과 같이 접두어 C가 붙는 Swift 타입과 대응된다. 이는 라이브러리 임포트시에 자동으로 맵핑이 적용되게 된다.

enum 타입

C의 enum 타입은 Swift의 enum 타입으로 변경된다. 특히 NS_ENUM 매크로를 통해 정의된 열거값들은,

  1. NS_ENUM 매크로의 첫번째 인자로 오는 타입이 연관타입이 되며
  2. NS_ENUM 매크로로 정의되는 타입명이 생략된 이름을 변경된다.

예를 들어

typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,    
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
};

이라고 명시된 경우, NSInteger -> Int 형으로 맵핑되므로

enum UITableViewCellStyle: Int {
    case Default
    case Value1
    case Value2
    case Subtitle
}

과 같은 식으로 변경된다. 이후 사용은 여느 swift enum 타입과 동일하게 쓰이게 된다.

포인터

Swift는 비록 C포인터를 제어할 수 있는 기능들(UnsafePointer 계열)을 제공하지만, 직접 포인터를 다루는 것을 권장하지는 않는다. T 타입 포인터에서 Type* 형은 UnsafeMutablePointer<T> 로 맵핑되며, const Type*UnsafePointer<T> 가 된다.

상수포인터

C함수 파라미터가 정의된 경우, 이는 다음과 같이 맵핑될 수 있다.

  • nilNULL 포인터가 된다.
  • UnsafeMutablePointer<T>, AutoreleasingUnsafeMutablePoiner<T>, UnsafePointer<T> 같은 값들로 변경된다.
  • &value 처럼 inout 표현으로 전달되는 값은 해당 값의 주소로 넘어간다.
  • [Type] 값은 C처럼 해당 배열의 시작 포인터로 전달된다.

만약 다음과 같은 Swift 코드가 있다고 하면…

void takeAPointer(const float const* x);

이는 Swift에서 다음 함수로 변환된다.

func takeAPointer(x:UnsafePointer<Float>) { .... }

그래서 다음과 같이 사용할 수 있다.

var x:Float = 0.0
var p:UnsafePointer<Float> = nil

takeAPointer(nil)
takeAPointer(p)
takeAPointer(&x)
takeAPointer([1.0, 2.0, 3.0])

실제로는

let x:Float = 24.5
withUnsafePointer(x, takeAPointer)

이런식으로 쓰기도 한다.

void* 타입을 받는 C 함수는 UnsafePointer<Void>가 되는데, 이 때는 T 타입의 종류를 가리지 않고 다음과 같은 형태로 쓸 수 있다.

var x: Float = 0.0, y: Int = 0
var p: UnsafePointer<Float> = nil, q: UnsafePointer<Int> = nil

takesAVoidPointer(nil)
takesAVoidPointer(p)
takesAVoidPointer(q)
takesAVoidPointer(&x)
takesAVoidPointer(&y)
takesAVoidPointer([1.0, 2.0, 3.0] as [Float])

let intArray = [1, 2, 3]
takesAVoidPointer(intArray)

변수 포인터

값이 변경될 수 있는 변수포인터는 UnsafeMutablePointer<T> 타입이며, 상수포인터와 동일한 형태로 변환될 수 있다. 단, 이 값은 자체적으로 mutating 될 수 있으므로 변수로 선언한 변수나 값들만 적용이 가능할 것이다.

auto-releasing unsafe mutable pointer

AutoreleasingUnsafeMuatblePointer<T> 타입은 (에구 길다..) Objective-C 객체 포인터에 대한 암시적인 포인터 변환을 사용하는 타입이다.

  • nil은 널 포인터로 전달된다.
  • inout 인자는 오토릴리즈 객체포인터로 전달된다.
  • UnsafeMutablePointer<T>는 그대로 전달된다.

inout 인자에 대한 설명이 모호해보이는데, 이는 소유되지 않는 버퍼로 복사되어 그 포인터가 전달된다. 그리고 함수가 리턴할 때, 다시 해당 변수로 값이 복사된다.

func takeAnARPtr(x: AutoreleasingUnsafeMutablePointer<NSDate?>) { ... }

이런 함수는 void takeAnARPtr(NSDate* x)로부터 왔을 것이다. 다음과 같은 방법들로 호출가능하다.

  1. nil 은 널포인터를 대신해서 전달한다.
  2. NSObject 계열의 객체는 AutoreleasingUnsafeMutablePoiner 타입이 되어 전달할 수 있다.
  3. NSObject? 타입에 대해서 &를 붙여서 전달할 수 있다.
var x: NSDate? = nil
var p: AutoreleasingUnsafeMutablePointer<NSDate?> = nil

takeAnARPtr(nil)
takeAnARPtr(p)
takeAnARPtr(&x)