참고로 비슷한 이름의 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) 가이드
0 comments:
댓글 쓰기