データシリアライズはいろんなケースで必要とされます。オブジェクトを一時的に保存、復元したり、ネットワークを通じて送受信したりするときなどです。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 = [[Person alloc] init]; data.name = [NSString stringWithFormat:@"%@", names[i]]; data.number = i; [dataArray addObject:data]; } MyData *my = [[MyData alloc] init]; my.person = dataArray; NSMutableData *data = [NSMutableData data]; NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; [encoder encodeObject:my forKey:@"my"]; [encoder finishEncoding]; [data writeToFile:path atomically:YES]; //[data writeToFile:path atomically:NO]; } @end int main(int argc, char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; SerWrite *ser = [[SerWrite alloc] init]; [ser saveData]; [pool drain]; return 0; } [/C] SerRead.m (ファイルから読み込み、デシリアライズして表示) [C] #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
gccgnustep-config --objc-flags
SerRead.m Person.m MyData.m -o SerRead -lobjc -lgnustep-base
さらに進んで、上記ソースのコメント部分を入れ替えて、ファイル入出力でなく標準入出力を採用すると、以下のようなコマンドでリダイレクトできます。
./SerWrite | ./SerRead
これはオブジェクトのパイプとなり、きちんと仕様をつくればWindowsのPowerShellもどきのようなことができます。xinetd(スーパーサーバ)を使って、ネットワークごしのパイプ接続をしても面白いかもしれません。