2014년 11월 26일 수요일

Swift/Objective-C 오브젝트 직렬화(Serialization)

직렬화(Serialization) 라는 표현은 대개 OOP언어에서 특정 클래스 오브젝트를 파일로 저장하거나 파일에서 읽어들일 때, 혹은 네트워크 등으로 전송하거나 받거나 하기 위해 특수하게 가공하는 것을 의미한다. Objective-C나 Swift에서는 이 부분에서 인코드(Encode), 디코드(Decode) 용어를 사용한다.

인코드(Encode)는 직렬화(Serialize) 한다는 의미로 보면 되고, 디코드(Decode)는 직렬화 된 데이터를 오브젝트로 풀어낸다(Deserialization)는 의미로 보면 된다.

NSObject를 다루다보면 KVC(Key-Value Coding) 라는 개념, 즉 Key와 Value를 매핑시키는 개념을 많이 봤을 것이다. 직렬화에서도 클래스 프로퍼티(혹은 멤버 변수)와 키 사이를 연결시어 주는 스타일이 사용된다.

Encode / Decode 기능 구현

직렬화 기능은 NSCoding 이라는 프로토콜을 기반으로 한다. 아래 예제는 SomePersonClass 라는 클래스를 정의하고 있고 이 클래스 오브젝트의 인코드(Encode)와 디코드(Decode) 코드를 구현하고 있다.

# Swift Code:
class SomePersonClass: NSObject, NSCoding {
    // Properties
    var name: String
    var age: Int
    var birthday: NSDate

    ...

    required init(coder aDecoder: NSCoder) {
        self.name = aDecoder.decodeObjectForKey("name") as String
        self.age = aDecoder.decodeIntegerForKey("age")
        self.birthday  = aDecoder.decodeObjectForKey("birthday") as NSDate
        super.init()
    }

    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(self.name, forKey: "name")
        aCoder.encodeInteger(self.age, forKey: "age")
        aCoder.encodeObject(self.birthday, forKey: "birthday")
    }

    ...
}
# Objective-C Code:
// Definitions(.h) -----

@interface SomePersonClass: NSObject 
...
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) NSDate *birthday;
...
@end

// Implementations(.m) -----

@implementation SomePersonClass

...

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        self.name = (NSString *)[aDecoder decodeObjectForKey:@"name"];
        self.age = [aDecoder decodeIntegerForKey:@"age"];
        self.birthday = [aDecoder decodeObjectForKey:@"birthday"];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeInteger:self.age forKey:@"age"];
    [aCoder encodeObject:self.birthday forKey:@"birthday"];
}

...

@end
NSCoding 프로토콜을 만족시키려면 두 가지 메소드를 작성해야 한다. 하나는 init(coder Coder: NSCoder) 혹은 initWithCoder: 이름을 가지는 생성자이다. 나머지 하나는 encodeWithCoder(aCoder: NSCoder) 혹은 encodeWithCoder: 라는 이름을 가지는 메소드이다.

Encode with Coder

Swift의 encodeWithCoder(aCoder: NSCoder) 메소드와 Objective-C의 encodeWithCoder: 메소드는 오브젝트를 직렬화 하려 할 때 자동으로 호출된다.

이 메소드의 구성은 aCoder를 이용해 클래스의 정보에 해당하는 프로퍼티를 인코딩 하는 것 뿐이다. 실제로 사용된 메소드를 보면 encodeObject니 encodeInteger니 하는 특정 타입을 인코딩 하는 메소드를 호출하고 있다.

이 외에도 NSCoder의 encode 메소드류는 타입에 따라 굉장히 많으니 적절한 것으로 골라서 사용하자. 기본 클래스류는 모두 정해진 타입 혹은 encodeObject 류를 이용하면 된다.

키 이름은 물론 원하는 키를 문자열로 적으면 된다.

Initialize with Coder

Swift의 init(coder Coder: NSCoder) 생성자와 Objective-C의 initWithCoder: 생성자는 직렬화 된 데이터에서 오브젝트를 만들어 낼 때 자동으로 호출되는 생성자이다.

이 생성자는 Encode 와는 반대로, 이번엔 aDecoder 즉 디코더를 이용해 인코딩 할 때와 동일한 타입으로 디코딩을 하도록 하는 코드가 구현되어야 한다. 위 예제 코드를 보면 인코딩 할 때의 타입과 디코딩 할 때의 타입이 동일하게 되어 있다.

당연하겠지만 디코딩 시 넘겨주는 키 이름은 인코딩 할 때 사용한 키와 동일한 이름을 사용해야 한다.

NSKeyedArchiver

이번에는 직렬화 된 오브젝트 데이터(NSData)를 얻는 방법이다. 다른 방법이 있을지도 모르겠지만 NSKeyedArchiver를 활용하는 편이 더 나은 것 같아서 소개한다.

아래 예제 코드는 SomePersonClass 타입의 오브젝트인 object 를 NSUserDefaults를 이용해 저장하는 예제이다.

# Swift Code:
let data = NSKeyedArchiver.archivedDataWithRootObject(object)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "object-key")
# Objective-C Code:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"object-key"];
이렇게 해서 최종적으로 data라는 NSData 타입의 변수에는 object가 직렬화 된 데이터 오브젝트가 할당되고, 이 data는 NSUserDefaults의 setObject 를 통해 시스템에 저장된다.

NSKeyedUnarchiver

직렬화를 했으니 이걸 푸는 방법도 소개해야 할 것이다. 이번에는 Unarchiver 이다.

아래 예제 코드는 위와 반대로 NSUserDefaults에서 NSData 타입의 데이터를 읽어서 이를 디코딩 하는 내용이다.

# Swift Code:
if let data: NSData = NSUserDefaults.standardUserDefaults().objectForKey("object-key") as? NSData {
    let object: SomePersonClass = NSKeyedUnarchiver.unarchiveObjectWithData(data) as SomePersonClass
}
# Objective-C Code:
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:@"object-key"];
SomePersonClass *object = [NSKeyedUnarchiver.unarchiveObjectWithData:data];
이렇게 하면 직렬화 된 데이터에서 클래스 오브젝트를 뽑아내는 것이 가능해진다.

댓글 없음 :