Swift :: Encode and Decode a value type's instance
임의의 Struct, Enum 타입을 인코딩하라.
임의의 값 타입을 인코딩하는 법을 찾아보자. (클래스의 경우에는 NSCoding이 있으니…) 먼저 인코딩이 필요한 함수를 생각해보자. NSData는 특정 포인터와 그 길이를 가지고 데이터를 만들 수 있다. NSData(bytes:UnsafePointer<T>, length:Int)
여기서 크기는 타입의 크기인데, C와 같이 Swift도 sizeof()
함수를 제공한다. (C에서는 연산자이다.)
Swift에서 inout T
타입과 UnsafePointer<T>
타입은 구분되므로 (T가 옵셔널인 경우에는 포인터로 간주될 수 있다.) 보조 함수인 withUnsafePointer<T>(_:_:)
함수를 사용하여 inout 으로 넘긴 T 타입을 포인터로 바꿀 수 있다.
따라서 인코드 함수는 다음과 같이
func encode<T>(var value:T) -> NSData {
let data = withUnsafePointer(&value){
(p:UnsafePointer<T>) -> NSData in
return NSData(bytes:p, length:sizeof(T.Type))
}
return data
}
작성할 수 있다. 디코드하는 함수는? 이건 영락없이 포인터를 만들어서 메모리를 신규로 할당하고 여기에 바이트를 채워넣는 수 밖에 없어보인다. (물론 NSValue로 만들어서 내놓는 방법이 있는데, Swift는 아직 @encode
를 쓸 수 없다.)
그래서 UnsafeMutablePointer<T>
타입을 이용해서 실제로 포인터를 다뤄보자.
UnsafeMutablePointer
이 타입은 T 타입에 대한 메모리 포인터를 나타내는 Swift 타입이다.
- advanceBy(_:) : n 개 만큼 뒤쪽의 요소를 가리키는 포인터를 리턴한다. n은 음수를 써서 앞쪽 포인터를 가리키게 할 수 있다.
- assignFrom(:count:), assignBackwardFrom(:count:) : memcpy와 비슷.
- dealloc() : 할당한 메모리를 해제한다.
- destroy() : 포인터가 가리키고 있는 위치의 객체를 파괴한다. 파괴된 메모리 영역을 다시 쓰이기 전에 반드시 초기화 되어야 한다.
- distanceTo(_:) : 특정 포인터와의 거리(T포인터의 개수)를 구한다.
- initialize(_:) : 주어진 값으로 해당 포인터를 초기화한다.
- initializeFrom(_:): 주어진 집합의 값들로 메모리를 초기화한다.
- initializeFrom(_:, count:):
- move(): T타입 값을 읽고 그 영역을 파괴한다.
- moveAssignFrom(_:,count:): 특정 소스의 값을 복사한다. (memmove와 비슷)
- moveInitializeFrom(:,count:), moveInitializeBackwardFrom(:,count:) : 특정 소스로부터 값을 거꾸로 복사해온다.
- predecessor() : 앞 위치
- successor() : 뒤 위치
- put(_:) : ?
- (static) alloc(_:) : N바이트만큼 메모리를 할당한다.
디코드 함수
디코드 과정은 다음과 같다. NSData
는 -getBytes:length:
메소드를 통해서 특정 길이만큼을 건네받은 포인터로 복사해주는 기능이 있다. 따라서 NSRange
에 대한 뮤터블한 포인터를 만들고 그 값을 복사한다. 그런 다음, move()
메소드를 통해 포인터 내의 값을 얻으면 된다.
func decode<T>(data:NSData) -> T {
var pointer = UnsafeMutablePointer<T>.alloc(sizeof(T.Type))
data(getBytes:pointer, length:data.length)
let result = pointer.move()
pointer.dealloc()
return result
}
이로써, Struct, Enum 등 Swift의 커스텀 타입에 대한 바이트 인코딩/디코딩을 할 수 있게 되었다.