Swift 속의 C Pointer 이야기 - UnsafePointer, UnsafeMutablePointer
UnsafeMutablePointer 는 지정된 타입의 포인터를 다루는 가장 일반적인 컨테이너이다. Mutable 이라는 이름에서 유추가 가능하겠지만, 이 포인터 컨테이너는 포인터가 가리키는 메모리를 조작(?)할 수 있는 녀석이다.
참고로 비슷한 이름의 UnsafePointer 는 메모리 할당과 해제 그리고 쓰기 기능이 제한되는 것을 빼면 동일한 인터페이스로 사용이 가능하다. 즉, UnsafeMutablePointer 를 알면 UnsafePointer 도 비슷하게 사용 가능하다. 제목에 있지만 실제 내용이 없는건 이런 이유에서다.
간단한 예제를 보자
참고1) defer 문은 함수나 메소드 블럭이 끝날 때 호출된다. 따라서 위의 deallocate 메소드는 모든 작업이 끝난 후 호출된다.
참고2) dump() 라고 정의한 클로져는 포인터의 내용물을 확인하기 위한 것인데 그냥 적기엔 중복되는 코드가 많기에 클로져로 구현했을 뿐이다.
dump가 실행될 때 내용을 보면 어떤 식으로 동작하는지 알 수 있다.
이 함수를 Swift 에서 쓰려면 아래와 같은 식으로 쓸 수 있다.
정말 간단한 예제인데도 좀 복잡해 보인다. 물론 Swift 에서 포인터는 직접 다룰 수는 없기에 이렇게 귀찮게 쓰는 것이다.
희망을 좀 주자면 위 코드는 최신 Swift 3 에서는 아래와 같이 쓸 수도 있다.
다만 이렇게 쓸 수 있는 경우는 좀 한정되어 있다는 점을 기억하자.
[돌아가기] Swift 속의 C Pointer 이야기 - 시작
[관련글] Swift 프로젝트에서 Objective-C 코드를 함께 사용하기
[관련글] 스위프트(Swift) 가이드
참고로 비슷한 이름의 UnsafePointer 는 메모리 할당과 해제 그리고 쓰기 기능이 제한되는 것을 빼면 동일한 인터페이스로 사용이 가능하다. 즉, UnsafeMutablePointer 를 알면 UnsafePointer 도 비슷하게 사용 가능하다. 제목에 있지만 실제 내용이 없는건 이런 이유에서다.
기본 API
간략하게 필수적인 것들만 정리하자면 아래와 같은 API를 거론 할 수 있을 것 같다.- 메모리 할당: allocate(capacity:)
- 메모리 해제: deallocate(capacity:)
- 포인터 증가: successor()
- 포인터 감소: predecessor()
- 포인터 엑세스: pointee or subscript
- 포인터 랜덤액세스: advanced(by:) or subscript(index)
간단한 예제를 보자
let count = 10 // Allocation(메모리 할당) let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count) // Deallocation(메모리 해제) defer { pointer.deallocate(capacity: count) } // Initialization(포인터가 가리키는 메모리를 0으로 채우기) pointer.initialize(to: 0, count: count) // Random access(n 번째 포인터가 가리키는 메모리 액세스) for i in 0..<count { pointer[i] = i } let dump: () -> () = { for i in 0..<count { print("\(pointer[i])") } } dump() // Access(포인터가 가리키는 메모리 액세스) pointer.pointee = 11 dump() // Access next pointer(다음 포인터가 가리키는 메모리 액세스) pointer.successor().pointee = 12 dump()이 예제는 포인터를 이용해 마치 10개의 아이템을 가지는 정수형 배열을 액세스 하는 듯한 사용법을 볼 수 있다. 주석으로 간단한 설명을 표기해 놨으니 읽기에는 어렵진 않을 것이다.
참고1) defer 문은 함수나 메소드 블럭이 끝날 때 호출된다. 따라서 위의 deallocate 메소드는 모든 작업이 끝난 후 호출된다.
참고2) dump() 라고 정의한 클로져는 포인터의 내용물을 확인하기 위한 것인데 그냥 적기엔 중복되는 코드가 많기에 클로져로 구현했을 뿐이다.
dump가 실행될 때 내용을 보면 어떤 식으로 동작하는지 알 수 있다.
- 첫 dump() 에서는 0, 1, 2, 3, … 9 까지가 콘솔에 표시된다.
- 두번째 dump() 에서는 11, 1, 2, … 9 가 콘솔에 표시된다.
- 세번째 dump() 에서는 11, 12, 2, … 9 가 콘솔에 표시된다.
C 함수와의 연동
아래와 같은 C 함수를 Swift 에서 사용하고 한다고 치자.void good_swap(int *a, int *b) { int c = *a; *a = *b; *b = c; }포인터를 활용한 아주 대표적인 스왑(두 값을 교환)함수이다. 물론 이거 말고도 비트와이즈 연산으로 3자 변수 없이 교환하는 것도 가능하겠지만 그런 귀찮고 가독성 떨어지는 짓을 하고 싶지는 않다.
이 함수를 Swift 에서 쓰려면 아래와 같은 식으로 쓸 수 있다.
let pointerA = UnsafeMutablePointer<Int32>.allocate(capacity: 1) pointerA.pointee = 10 let pointerB = UnsafeMutablePointer<Int32>.allocate(capacity: 1) pointerB.pointee = 20 print("A = \(pointerA.pointee), B = \(pointerB.pointee)") defer { pointerA.deallocate(capacity: 1) pointerB.deallocate(capacity: 1) } good_swap(pointerA, pointerB) print("A = \(pointerA.pointee), B = \(pointerB.pointee)")Int32 가 쓰이고 있는데 C에서 사용하는 int 가 Swift 에서는 Int32 로 번역되기 때문이다.
다만 이 타입 번역은 확정적인 것은 아니라는 점을 명심하자. C에서도 명확한 타입을 쓰는게 좋다. C 에서도 int32_t 라는 확실하게 구분되는 타입을 쓸 수 있다.첫 print 문과 두번째 print 문에서 pointerA 가 가리키는 메모리의 데이터와 pointerB가 가리키는 메모리의 데이터가 바뀌었다는 점을 알 수 있다.
정말 간단한 예제인데도 좀 복잡해 보인다. 물론 Swift 에서 포인터는 직접 다룰 수는 없기에 이렇게 귀찮게 쓰는 것이다.
희망을 좀 주자면 위 코드는 최신 Swift 3 에서는 아래와 같이 쓸 수도 있다.
var a = Int32(10) var b = Int32(20) print("A = \(a), B = \(b)") good_swap(&a, &b) print("A = \(a), B = \(b)")사실 동일한 코드다. 단순한 포인터는 그냥 Swift 의 inout 방식 즉 앰퍼선드(&)를 붙여서 넘기는 것이 가능하다. 대신 포인터로 넘기기 위해서는 반드시 변수(var)로 선언되어야만 한다. let 으로 생성된 상수는 inout 으로 넘기는 것 자체가 불가능하다.
다만 이렇게 쓸 수 있는 경우는 좀 한정되어 있다는 점을 기억하자.
[돌아가기] Swift 속의 C Pointer 이야기 - 시작
[관련글] Swift 프로젝트에서 Objective-C 코드를 함께 사용하기
[관련글] 스위프트(Swift) 가이드
댓글