카테고리 보관물: iOS앱 만들기

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

SKNode를 이름으로 찾기

SKNode의 트리탐색

씬의 노드트리에서 어떤 노드를 탐색하고 특정하는 일이 종종 필요한데, 상위 노드에 대한 참조만 가지고 하위 노드를 찾기 위해서는 각 노드에 미리 이름을 부여하는 것이 필요하다. 노드의 .name 프로퍼티는 노드의 이름을 저장하며 알파벳과 숫자로만 이루어지며 공백이나 구두점등은 포함해서는 안된다.

노드의 이름은 중복되어도 상관없다.

playerNode.name = "player"
monsterNode1.name = "goblin"
monsterNode2.name = "ogre"

위 예에서 게임에 등장하는 고블린이 여러마리라면 이 고블린 노드의 이름은 제각각 유니크하기가 힘들것이며, "goblin"이라는 공통의 이름을 갖게 된다. 하지만 플레이어 캐릭터는 게임내에서 유니크하므로 다른 노드와 이름이 중복되지 않아야 한다.

노드 이름이 요긴하게 쓰이는 경우는 다음과 같은 경우가 있다.

  • 두 노드가 충돌/접촉했을 때, 델리게이트에서는 두 오브젝트를 받게 된다. 이 때 두 오브젝트 A, B 중 어느것이 비행기이고 어느것이 미사일인지 구분하는 것은 이름을 통해서 구분하는 것이 가장 쉽다.
  • 특정한 이름을 갖는 노드를 찾을 수 있다.SKNode 클래스는 이름과 관련된 다음의 메소드들을 제공한다.
    • childNode(withName:)은 매치되는 이름을 갖는 첫번째 노드를 리턴한다. 이는 보통 유니크한 이름을 갖는 노드를 찾을 때 사용한다.
    • enumerateChildNodes(withName:using:)은 매치되는 이름을 갖는 노드들을 순회한다. 이는 중복된 이름을 갖는 노드를 찾을 때 사용한다.
    • subscript(_:) 역시 매치되는 노드의 배열을 얻을 때 사용한다.
var playerNode: SKNode? { return childNode(withName:"player") }

고급 검색

기본적인 탐색은 주어진 이름 문자열과 동일한 이름을 갖는 서브 노드를 탐색하는데 쓰인다. 하지만 SpriteKit은 고급 검색을 수행할 수 있는 보다 표현적인 탐색 문법을 제공한다. 예를 들어 특정 노드의 전체 자식 노드 중 특정 패턴을 만족하는 노드를 찾는 것이 가능하다.

/ : 부모-자식 관계의 구분자이다. 패턴의 맨 앞에 위치하면 루트 노드에서 출발하여 탐색하고, 그외의 경우에는 /의 왼쪽에 있는 부모노드의 자식 노드에 대해서 탐색한다.
// : 재귀 탐색을 수행한다. 즉 자식의 자식, 자식의 자식의 자식으로 트리의 끝까지 모두 탐색한다.
.: 현재 노드를 말한다.
.. : 현재 노드의 부모노드를 말한다.
*: 0개 이상의 글자에 매치한다.
[ ]: 컴마나 대쉬로 구분된 단어 들 중 하나에 매치할 수 있다.
알파뉴머릭: 이름에 매치하는 패턴이다.

다음은 패턴을 사용한 탐색의 예제이다.

  1. MyName: MyName이라는 이름과 일치하는 노드
  2. /MyName: 노드의 직계 자식 중에서 이름이 MyName과 일치하는 노드
  3. //MyName: 노드의 자손 전체 중에서 이름이 MyName과 일치하는 노드
  4. .//MyName: 현재 노드의 전체 자손 중에서 이름이 MyName과 일치하는 노드
  5. //Myname/..: 전체 노드 중에서 이름이 MyName과 일치하는 자식을 갖고 있는 부모 노드
  6. //*: 현재 트리의 모든 노드
  7. A[0-9]: 현재 노드의 자식 중에서 이름이 A0, A1, A2…, A9인 노드들 중 하나
  8. Abby/Normal: 현재 노드의 자식 중에서 Abby와 일치하는 노드의 자식 중에서 다시 이름이 Normal인 노드
  9. //Abby/Normal: 전체 노드 중에서 Abby라는 노드를 부모로 갖는 Normal이라는 이름의 노드

https://medium.com/swift-programming/build-tic-tac-toe-with-ai-using-swift-25c5cd3085c9#.vujee1siu

다음은 틱택토 게임을 구현하는 코드 예제 중의 일부이다. SKScene의 오버라이드인데, SKSceneSKNode의 서브 클래스 중 하나라는 점을 상기하자.


override func didMove(to view: SKView) {
    /// 이름이 "grid"로 시작하는 모든 노드에 대해서 순회한다.
    /// 
    self.enumerateChildNodes(withName: "//grid*") { (node, stop) in
        if let node = node as? SKSpriteNode {
            node.color = UIColor.clear()
        }
    }
    /// ...
}