Objective-C :: 임의의 데이터 타입을 NSData로 인코딩하기

NSValue NSData 변환

NSValueNSData로 변환하는 방법에 대해 생각해보자. 코어데이터 등에서 표준으로 지원하는 타입이 아닌 경우, NSData를 이용해서 이진데이터로 저장하는 방법이 있는데, 예를 들어서 NSRange(이건 C구조체이다.)를 이진데이터로 바꾸는 것은 다음과 같이 하면 된다.

NSRange r = NSMakeRange(0, 10);
NSValue* v = [NSValue valueWithRange:r];
NSData* d1 = [NSKeyedArchiver archivedDataWithRootObject:v];

구조체나 스칼라값 타입은 NSValue로 감싼 다음 이진데이터로 직렬화할 수 있다. NSValue 역시 NSCoding 프로토콜을 따르므로 위와 같이 처리하면 되는데….

NSLog(@"%d", [d1 length]);
// 128

근데 변환한 크기가 무려 128 바이트나 된다. 고작 float타입의 멤버 두 개를 가진 구조체(16바이트)가 자리수가 2개나 차이나게 뻥튀기 된다.

NSUInteger size = 0;
NSUInteger tAlignment = 0;
const char* vType = v.objCType;
NSGetSizeAndAlignment(vType, &size, NULL);

NSLog(@"%d", size);
// 16

원래는 16바이트짜리 구조체였던 것이다. NSData는 최종적으로 연속된 메모리 영역을 래핑하는 객체이므로, 시작 주소와 크기를 알면 그 블럭을 감싼 데이터 객체를 만들 수 있다. 따라서 아래와 같이하여…

NSData *d2 = [NSData dataWithBytes:&r length:size];
NSLog(@"%d", [d2 length]);
// 16

이렇게 좀더 효율적으로 구조체를 이진데이터로 만들 수 있다. 이를 다시 구조체로 원복하는 과정은

void* ptr = malloc(sizeof(NSRange));
[data getByte:ptr length:[data.length]];
NSRange r = ((NSRange)ptr);

이면 충분하다.

여기서 사용된 NSGetSizeAndAlignment() 함수를 이용하면 임의의 구조체나 타입에 대해서 (사실 sizeof()를 써도 되는 것 아닌지?) 인코딩 문자열을 통해 정확한 사이즈를 구할 수 있고, 이를 이용해서 NSData로 변환 가능하다.

문제는 Swift의 경우에는 @encode 디렉티브가 없다는 것과 C 포인터를 쉽게 다루기가 어렵다는 것인데… 이에 대해서는 별도로 포스팅하도록 하겠다.