Raw 포인터 사용에 대해

https://developer.apple.com/documentation/swift/unsaferawpointer

UnsafeRawPointer 타입은 자동 메모리 관리, 타입 안정성 및 메모리 정렬 보장이 되지 않는 원시 포인터 액세스를 제공합니다. 이 타입을 사용하려면 누수를 피하고, 할당된 메모리의 라이프 사이클을 직접 관리해야 하며, 그 외의 정의되지 않는 동작들을 회피해야 합니다. 수동으로 직접 관리하는 메모리 영역은 특정한 타입에 바운드되거나, 타입이 지정되지 않을 수도 있습니다. 메모리 영역에서 해당 영역이 특정 타입에 묶여있는지 여부와 무관하게 순수 바이트를 액세스하려할 때 UnsafeRawPointer 타입을 사용할 수 있습니다.

막 할당된 Raw 메모리는 타입화되지도 초기화되지도 않은 상태입니다. 이 메모리는 타입화된 연산을 사용하기 전에 반드시 초기화되어야 합니다. (초기화되려면 초기값을 가져야하고, 이는 타입화를 수반해야한다는 의미가 됩니다.) 초기화되지 않은 상태에서 특정 타입에 바인등하려면 bindMemory(to: count:)를 사용합니다. 이 메소드는 타입화된 포인터를 반환하며, 이후에는 해당 포인터를 사용해야 합니다.

일단 메모리가 특정 타입에 바인드되면, 초기화 여부와 상관없이 해당 포인터는 타입화된 포인터(UnsafePointer/UnsafeMutablePointer)로 액세스됩니다. 초기화, 메모리정렬, 해제와 같은 작업들은 모두 이들 타입화된 포인터의 메소드를 사용하여야 합니다.

특정 타입에 바인드된 메모리를 다른 타입에 리바인드하려면, 이 메모리를 쓸 수 없는 상태로 되돌려지거나(deinitialized), 가리키는 타입이 trivial해야 합니다. (trivial하다는 것은 다른 타입을 간접적으로 참조하지 않는다는 의미입니다.) 초기화를 취소하는 것은 해당 포인터를 사용불가하게 만드는 것일 뿐, 바인드된 타입을 제거하지는 않습니다. 이 상태에서 메모리는 다시 같은 타입으로 초기화될 수도 있고, 다른 타입으로 바인드되거나 해제될 수 있습니다.

특정 타입에 바인드된 후에 해당 포인터로부터 바이트를 읽을 때에는 메모리 정렬과 관련된 요구사항들을 준수해야 합니다.

액세스

  • load(fromByteOffset: as:) – 포인터로부터 offset 만큼 떨어진 위치에서 type으로 간주하고 읽어들인 T 타입 값을 리턴합니다. 타입화된 포인터의 pointee에 해당합니다.
  • advanced(by:) – 주어진 offset 만큼 떨어진 곳의 포인터를 리턴합니다.
  • bindMemory(to: capacity:) – raw 메모리를 주어진 타입의 메모리 포인터로 바인드합니다.
  • assumingMemoryBound(to:) – 현재 포인터가 이미 특정 타입에 바운드 되었다고 가정하고 T타입 포인터를 리턴합니다.

가변 포인터는 특정 위치에 값을 쓰는 액션을 허용합니다. 여기에는 복사, 해제 및 초기화동작이 포함됩니다.

  • initializeMemory(as: from: count:) – 특정한 T 타입 포인터로부터 정해준 개수만큼의 값을 가져와서 타입화하고 초기화합니다. 초기화가 완료되면 T 타입 가변 포인터를 리턴합니다.
  • initializeMemory(as: repeating: count) – 연속된 메모리가 아닌 단일 값을 반복하여 메모리를 초기화합니다. 역시 T 타입 가변 포인터를 내놓습니다.
  • storeBytes(of: toByteOffest: as:) – 해당 위치(혹은 오프셋위치)에 T 타입 값을 씁니다.
  • copyMemory(from: byteCount:) – 다른 raw 포인터로부터 정해준 바이트수만큼의 데이터를 복사해옵니다.
  • deallocate() – 이전에 할당한 메모리를 해제합니다.
  • moveInitializeMemory(as: from: count:) – 다른 T 타입포인터로부터 지금 메모리를 타입화하고 초기화합니다. 그리고 참조한 from 포인터는 사용 불가한 것으로 상태를 변경합니다.

생성

불변 포인터는 T타입포인터와 그 호환포인터, 그리고 가변 Raw 포인터등으로부터 인스턴스를 생성할 수 있습니다. 가변포인터의 경우, init(mutating:)을 사용하여 기존 불변포인터로부터 새로운 가변포인터를 생성할 수 있습니다.

가변포인터는 아예 새로운 메모리 공간을 allocate(byteCount:, alignment:)를 사용하여 할당받으면서 생성할 수도 있습니다.

let bytesPointer = UnsafeMutableRawPointer.allocate(
                      byteCount: 4, alignment: 1)
bytesPointer.storeBytes(of: 0xFFFF_0203, as: UInt32.self)

let x = bytesPointer.load(as: UInt8.self) // 3
let offsetPointer = bytesPointer + 2
let y = offsetPointer.load(as: UInt16.self) // 65535

참고로 x86 아키텍처는 리틀 엔디언을 사용하기 때문에, UInt32는 아래쪽 바이트를 먼저 쓴다.

버퍼

바이트의 배열처럼 다룰 수 있게 하는 UnsafeRawBufferPointer 타입도 존재합니다. 각각의 바이트는 1바이트이기 때문에 Int8이나 UInt8 등의 값으로 액세스할 수 있습니다.