카테고리 보관물: Objective-C

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 계열의 타입도 마찬가지다. 

(Swift) 에러 핸들링 기법

에러 핸들링은 프로그램 내에서 에러가 발생한 상황에 대해 대응하고 이를 복구하는 과정이다. Swift는 에러를 던지고, 캐치하고, 이전하며 조작할 수 있는 기능을 언어의 기본 기능으로 지원한다.

어떤 동작들은 항상 유용한 결과를 내놓거나 완전하게 실행되는 것이 보장되지 않는다. 결과가 없는 경우에 이를 표현할 수 있는 옵셔널 타입을 사용할 수도 있지만, 처리 자체가 실패하는 경우, 어떤 원인으로 실패하였는지를 알면 코드가 이에 적절히 대응할 수 있도록 할 수 있기에 그 원인을 하는 것은 유용하다.

예를 들어 디스크상의 파일로부터 데이터를 읽고 처리하는 작업을 생각해보자. 여기에는 작업이 실패할 수 있는 경우가 꽤 많다. 주어진 경로에 파일이 존재하지 않거나, 파일을 읽을 수 있는 권한이 없거나 혹은 파일이 호환되는 포맷으로 인코딩 되어 있지 않을 수도 있다. 이런 상황들을 구분하는 것은 프로그램으로 하여금 에러를 스스로 해결하거나, 자동으로 해결할 수 없는 에러에 대해서 사용자에게 보고할 수 있게끔 한다. 계속 읽기