NSData Serialization

データシリアライズはいろんなケースで必要とされます。オブジェクトを一時的に保存、復元したり、ネットワークを通じて送受信したりするときなどです。JavaScriptのJSONなどはJava Script のObjectを記述したソースコード(テキストデータ)をそのままリクエスト・レスポンスで扱うので、目でみてとてもわかりやすい例です。最近では、MessagePackというJSONライクなバイナリシリアライゼーションがホットです。これはまたの機会に取り上げたいと思います。
今日は、Objective-Cを使ったシリアライゼーションをテストしてみました。
なぜ、ObjC? というと、最近、Objective-C / Linux を再評価しているからです。

環境 : GNUStep / Ubuntu 14.04

NSProxy / NSConnection
http://decode.red/net/archives/117

この記事と同じ環境を使っています。Objective-Cといえば、Mac, iPhoneというイメージが強いですが、もともとはNeXTSTEPのもので、ダイナミックな特徴がかなり強力です。これがLinuxで使えるので利用価値があると思いました。またObjective-CスキルがSwiftによって無駄になるということもなくなりますね。(?)

Person.h (シリアライズするデータはNSCoding Protocolを実装する必要があります。)

#import <Foundation/Foundation.h>

@interface Person : NSObject<NSCoding>
{
        NSString  *name;
        NSInteger number;
}
@property (nonatomic, assign) NSString *name;
@property (nonatomic, assign) NSInteger number;
@end

MyData.h (Personオブジェクトを格納する配列)

#import <Foundation/Foundation.h>
@interface MyData : NSObject<NSCoding>
{
        NSArray  *person;
}
@property (nonatomic, assign) NSArray *person;
@end

Person.m

#import "Person.h"

@implementation Person
@synthesize name, number;
- (void)encodeWithCoder:(NSCoder *)code
{
    [code encodeObject:self.name forKey:@"name"];
    [code encodeInteger:self.number forKey:@"number"];
}
- (id)initWithCoder:(NSCoder *)decode
{
    self = [super init];
    if (self) {
        self.name = 
             [decode decodeObjectForKey:@"name"];
        self.number  = 
             [decode decodeIntegerForKey:@"number"];
    }
    return self;
}
@end

MyData.m

#import "MyData.h"

@implementation MyData
@synthesize person;
- (void)encodeWithCoder:(NSCoder *)code
{
    [code encodeObject:self.person forKey:@"person"];
}
- (id)initWithCoder:(NSCoder *)decode
{
    self = [super init];
    if (self) {
        self.person = 
            [decode decodeObjectForKey:@"person"];
    }
    return self;
}
@end

SerWrite.m (シリアライズしてファイルに保存)

#import <Foundation/Foundation.h>
#import "Person.h"
#import "MyData.h"

//static NSString *path = @"/dev/stdout";
static NSString *path = @"./store.data";
@interface SerWrite : NSObject
@end

@implementation SerWrite
- (void) saveData
{
    NSMutableArray *dataArray = [NSMutableArray array];
    int i;
    NSString *names[] = {@"Steve", @"Bill", @"Larry", @"Mark", @"Elon"};

    for (i = 0; i < 5; i++) {
        Person *data = &#91;&#91;Person alloc&#93; init&#93;;
        data.name = &#91;NSString stringWithFormat:@"%@", names&#91;i&#93;&#93;;
        data.number  = i;
        &#91;dataArray addObject:data&#93;;
    }
    MyData *my = &#91;&#91;MyData alloc&#93; init&#93;;
    my.person = dataArray;

    NSMutableData *data = &#91;NSMutableData data&#93;;
    NSKeyedArchiver *encoder = &#91;&#91;NSKeyedArchiver alloc&#93; initForWritingWithMutableData:data&#93;;
    &#91;encoder encodeObject:my forKey:@"my"&#93;;
    &#91;encoder finishEncoding&#93;;
    
    &#91;data writeToFile:path atomically:YES&#93;;
    //&#91;data writeToFile:path atomically:NO&#93;;
}
@end

int main(int argc, char *argv&#91;&#93;) {
    NSAutoreleasePool *pool = &#91;&#91;NSAutoreleasePool alloc&#93; init&#93;;

    SerWrite *ser = &#91;&#91;SerWrite alloc&#93; init&#93;;   
    &#91;ser saveData&#93;;
    &#91;pool drain&#93;;
    return 0;
}
&#91;/C&#93;

SerRead.m (ファイルから読み込み、デシリアライズして表示)
&#91;C&#93;
#import <Foundation/Foundation.h>
#import "Person.h"
#import "MyData.h"

static NSString *path = @"./store.data";

@interface SerRead : NSObject
@end

@implementation SerRead
- (void)loadData
{
    NSMutableData *data = [NSMutableData dataWithContentsOfFile:path];

    //NSFileHandle *stdIn = [NSFileHandle fileHandleWithStandardInput];
    //NSMutableData *data = [NSMutableData dataWithData:[stdIn readDataToEndOfFile]];

    NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    MyData *my = [decoder decodeObjectForKey:@"my"];
    [decoder finishDecoding];
    
    NSLog(@"%@", my.person);

    for(Person *o in my.person){
        NSLog(@"(%d) name: %@ ",[o number] + 1, [o name]);
    }
}
@end

int main(int argc, char *argv[]) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    SerRead *ser = [[SerRead alloc] init];
    [ser loadData];
    [pool drain];
    return 0;
}

コンパイル

gcc gnustep-config --objc-flags SerWrite.m Person.m MyData.m -o SerWrite -lobjc -lgnustep-base
gcc gnustep-config --objc-flags SerRead.m Person.m MyData.m -o SerRead -lobjc -lgnustep-base

実行結果
nsdata01

さらに進んで、上記ソースのコメント部分を入れ替えて、ファイル入出力でなく標準入出力を採用すると、以下のようなコマンドでリダイレクトできます。

./SerWrite | ./SerRead

これはオブジェクトのパイプとなり、きちんと仕様をつくればWindowsのPowerShellもどきのようなことができます。xinetd(スーパーサーバ)を使って、ネットワークごしのパイプ接続をしても面白いかもしれません。

About

Categories: 未分類 タグ: