iOS앱 만들기, Swift

OpaquePointer

OpaquePointer

OpaquePointer는 Swfit2.x 에서 COpaquePointer가 이름이 바뀐 타입으로 불투명한1 C 포인터를 감싸는 래퍼 타입이다.1 타입을 반입할 수 있는 구조체의 포인터라면 원 구조체 Tc타입을 Swift 로 반입할 수 있고, 이 경우에는 UnsafePointer<Tc> 의 형태로 쓸 수 있다. 하지만 원 타입을 Swift가 이해할 수 없다면, 그 타입의 크기를 결정할 수 없으므로 이에 대한 포인터 역시 Swift 내부에서 결정하는 것은 어렵다.

물론 UnsafePointer<Void> 타입을 이용하는 것도 가능하다 생각할 수 있지만, 이 경우에는 C에서 void * 타입이며, 어찌됐든 pass 시점에 캐스팅해야 하는 한계가 있다. (그리고 원 타입을 알 수 없으면 캐스팅도 불가능하다.)

init: 은 다음과 같은 인자들을 받을 수 있다. (다만 실제로 생성할 일이 있을지는….)

  • UnsafeMutablePointer<T>, UnsafeMutablePointer<T>?
  • UnsafePointer<T>, UnsafePointer<T>?
  • (bitpattern: Int), (bitpattern: UInt)
  • UnsafeRawPointer, UnsafeRawPointer?
  • UnsafeMutableRawPointer, UnsafeMutableRawPointer?

UnsafeRawPonter는 타입이 지정되지 않은 데이터를 액세스하는 raw 포인터 타입이며, 사실상 C의 void * 타입 포인터를 날 것 그대로 엑세스하는 것과 유사하다.

UnsafePointer 패밀리와의 차이

UnsafePointer<T>, UnsafeMutablePointer<T>는 사실상 독립타입이 아닌 특정 Swift 타입에 대한 포인터 래퍼의 개념이므로, 이는 Swift 타입에 대한 포인터여야 한다. 기본적으로 C의 원시타입은 대부분 Swift 의 타입과 맵핑이 가능하지만, C의 구조체는 Swift의 구조체와 다르다. 따라서 UnsafePointer<T> 패밀리에 속하는 타입들은 C-API의 배열이나 포인터를 다루는 API와 연계할 때 사용하며, OpaquePointer는 알 수 없는 구조체 타입에 대한 포인터로 사용된다.

예시

sqlite3의 API를 Swift 내에서 이용하는 상황을 가정해보자.2

데이터베이스에 접근하기 위해서는 연결에 대한 핸들러가 필요한데, sqlite3의 API에서 sqltie_open() 함수는 데이터베이스 파일의 경로문자열을 받아서 해당 파일을 열고, 그 연결 핸들러를 리턴한다. 이 때 핸들러는 void *로 캐스팅된 sqlite3 객체 포인터이며, 따라서 이를 사용하기 위해서는 OpaquePointer를 써야 한다.

class DBConnector {
  lazy var db: OpaquePointer = { [unowned self] in
    let _db: OpaquePointer? = nil
    if sqlite3_open(self.db_path, &_db) == SQLITE_OK {
      return _db!
    }
    return nil
  }()
}

  1. 여기서 불투명한 C 포인터란, Swift로 반입될 수 없는 구조를 가진 C 구조체를 말한다. 
  2. 이 내용은 Swift에서 Sqlite3 사용하기 글에서도 다뤄진다. 

  1. 이름만 바뀐 것 외에도 nil 값을 갖기 위해서는 명시적으로 옵셔널로 선언해야 하는 차이도 있다. 이는 UnsafePointer 계열의 타입도 마찬가지다.