태그 보관물: swift

IUO 를 인자로 받는 함수 (Swift)

IUO타입을 인자로 선언한 함수들이 있는데, 이들은 주로 Objective-C/C API에서 발견된다. Objective-C에서 임의의 객체를 의미하는 id 타입은 항상 nil 일 수 있기 때문에 파라미터 타입을 AnyObject 가 아닌 기대하는 타입의 옵셔널로 받는 것이다. 하지만 이러한 API의 내부에서는 대부분 해당 값에 대해서 null 검사를 엄밀히 하지 않거나 하지 않아도 되는 경우가 많다.1

또한 C-API에서도 흔히 볼 수 있다. 이들은 임의의 포인터 혹은 불투명 타입[^1]을 인자로 받는데, C언어의 특성상 nil을 다른 값과 구분할 수 없는데, Swift 입장에서는 nil이 곧 NULL이기 때문에 옵셔널을 보낼 수 있게 되기 때문이다. 따라서 어떠한 API의 인자가 !으로 선언된 암묵적 언래핑 옵셔널을 사용한다고 할 때, 이를 사용하는 측면에서는 다음의 당연한 두 가지 접근법 중 하나를 취하게 된다.

  1. 옵셔널에 대해서 nil 체크를 하고 언래핑하여, 최종적으로는 옵셔널이 아닌 타입의 값만 넘긴다.
  2. 옵셔널 값을 그냥 넘겨준다.

2의 경우에는 자신이 무엇을 하고 있는지 정확히 파악되는 경우에만 시도해야 한다. Objective-C/C API 내부에서는 받은 인자의 값이 NULL이더라도 어떠한 동작을 하게끔 시도할지는 몰라도, 그것은 감춰져서 알 수 없으며 혹시라도 nil 값이 그대로 넘어간 경우에는 런타임 에러로 직결되기 때문이다.

많은 C/Objective-C API에서 이러한 타입을 볼 수는 있다. 하지만 그것은 언어의 기능이 불안정성을 깔끔하게 덜어내지 못하고 많은 부분의 불안요소를 컴파일 타임을 넘어 런타임까지 짊어지고 갈 수 밖에 없는 한계를 가지기 때문에 그러한 디자인을 갖게 된 것이다. Swift는 언어 수준에서 불안정성을 최소화하고 컴파일 타임에서 발생할 수 있는 최대한의 범위의 에러를 미리 찾을 수 있는 기능을 제공하고, 지원한다. 그것을 이용하고 지키는 습관은 단순히 타이핑을 몇 자 덜하는 것보다 훨씬 더 생산적이며, 나아가 C가 아닌 Swift의 디자인과 패러다임을 통한 사고의 틀을 만드는 것에 보다 빨리 익숙해지게 한다.

Swift에서 T! 타입을 인자로 받는 함수를 정의하기

이런 타입 시그니처는 함수 작성시에 당연히 쓸 수 있는 표현이기 때문에 그러한 타입의 인자를 받는 함수를 정의할 수는 있다. 하지만 앞에서도 말했듯이 그럴 필요가 있을 케이스가 있을 가능성이 0%이다. 받으려는 값이 항상 있어야하는 경우에는 T 타입을 쓰면 되고, 없거나 실패할 수 있는 가능성이 있다면 옵셔널을 쓰면 된다. 그 외의 경우는 없다.


  1. Objective-C에서는 nil에 어떤 메시지를 보내면 그냥 무시되기 때문에. 

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