読者です 読者をやめる 読者になる 読者になる

24/7 twenty-four seven

iOS/OS X application programing topics.

オブジェクトの状態をファイルに保存・復元する。(シリアライズ・デシリアライズ)

Cocoa Objective-C

参考サイト

Cocoaフレームワークには、もともとオブジェクトをシリアライズするための仕組みが用意されているので簡単です。
NSCodingプロトコルで定義されているメソッドを実装すれば、オブジェクトがシリアライズ可能になります。


例えば、下のような設定画面の値を保存するときに便利です。

設定の保存にはもうひとつ、NSUserDefaultsを使う方法もあります。
しかし、設定画面がiPhoneの「設定」のところになってしまい、設定項目に気づかないことがあったり、設定を変更するためにはアプリケーションを終了しなければならないなど、不便なので私は使いません。

手順

  • シリアライズされるクラスにNSCodingプロトコルのメソッドを実装する。
  • NSKeyedArchiverで保存
  • NSKeyedUnarchiverで復元

NSCodingプロトコルの実装

下記の2つのメソッドを実装します。

  • encodeWithCoder:
  • initWithCoder:

Cocoaフレームワークのたいていのクラス(NSString, NSDictionary, NSArray, ...)はNSCodingプロトコルを実装しています。
ほとんどの場合は自分で作ったクラスのインスタンス変数を順番に保存するという実装になります。


先ほどの設定画面の値を保存するために、以下のクラスを用意します。
設定項目に対応したインスタンス変数を持つだけのクラスです。

Settings.h
#import <UIKit/UIKit.h>

@interface Settings : NSObject <NSCoding> {
	NSString *area;
	NSInteger lhour;
	NSString *primeTimeFrom;
	NSString *primeTimeTo;
	NSMutableArray *keywordHistory;
	NSMutableArray *orderOfViewControllers;
}

@property (nonatomic, retain) NSString *area;
@property (nonatomic) NSInteger lhour;
@property (nonatomic, retain) NSString *primeTimeFrom;
@property (nonatomic, retain) NSString *primeTimeTo;
@property (nonatomic, retain) NSMutableArray *keywordHistory;
@property (nonatomic, retain) NSMutableArray *orderOfViewControllers;

@end

実装部は以下のようになります。
initとdeallocに加えて、encodeWithCoder:、initWithCoder:メソッドを実装します。

Settings.m
#import "Settings.h"

@implementation Settings

@synthesize area;
@synthesize lhour;
@synthesize primeTimeFrom;
@synthesize primeTimeTo;
@synthesize keywordHistory;
@synthesize orderOfViewControllers;

- (id)init {
	if (self = [super init]) {
		self.area = @"008";
		self.lhour = 2;
		self.primeTimeFrom = @"19:00";
		self.primeTimeTo = @"23:00";
		keywordHistory = [[NSMutableArray alloc] initWithCapacity:10];
		orderOfViewControllers = nil;
	}
	return self;
}

- (id)initWithCoder:(NSCoder *)coder {
	area = [[coder decodeObjectForKey:@"area"] retain];
	lhour = [coder decodeIntForKey:@"lhour"];
	primeTimeFrom = [[coder decodeObjectForKey:@"primeTimeFrom"] retain];
	primeTimeTo = [[coder decodeObjectForKey:@"primeTimeTo"] retain];
	keywordHistory = [[coder decodeObjectForKey:@"keywordHistory"] retain];
	orderOfViewControllers = [[coder decodeObjectForKey:@"orderOfViewControllers"] retain];
	return self;
}

- (void)encodeWithCoder:(NSCoder *)encoder {
	[encoder encodeObject:area forKey:@"area"];
	[encoder encodeInt:lhour forKey:@"lhour"];
	[encoder encodeObject:primeTimeFrom forKey:@"primeTimeFrom"];
	[encoder encodeObject:primeTimeTo forKey:@"primeTimeTo"];
	[encoder encodeObject:keywordHistory forKey:@"keywordHistory"];
	[encoder encodeObject:orderOfViewControllers forKey:@"orderOfViewControllers"];
}

- (void)dealloc {
	[orderOfViewControllers release];
	[keywordHistory release];
	[primeTimeTo release];
	[primeTimeFrom release];
	[area release];
	[super dealloc];
}

@end

encodeWithCoder:の実装

encodeWithCoder:にオブジェクトを保存する処理を書きます。
encode***:forKey:メソッドは、変数の型に応じてencodeObject:forKey:、encodeInt:forKey:などを使い分けます。
キーとなる文字列は何でも構いませんが、復元時のキー(decodeObjectForKey:のパラメータ)と対応させる必要があります。

initWithCoder:の実装

シリアライズしたオブジェクトを復元する処理です。
decode***ForKey:は変数の型に応じて使い分けます。encode***:forKey:と同様です。
キーとなる文字列はシリアライズ時のキーと同じキーを指定します。
復元したオブジェクトは、インスタンス変数として保持するので、retainメッセージを呼ぶ必要があります。
retainしない場合は、メソッドを抜けると値が失われます。
変数がプロパティの場合は、以下のようにプロパティとして代入すればretainする必要はありません。(自動的にretainが呼ばれる)

self.area = [coder decodeObjectForKey:@"area"];

オブジェクトをファイルに書き出す

ここまでで、Settingsクラスはシリアライズ可能になっています。
実際にインスタンスをファイルに保存する処理は以下のようになります。

- (void)loadSettings {
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	NSString *documentDirectory = [paths objectAtIndex:0];
	NSString *path = [documentDirectory stringByAppendingPathComponent:@"settings.dat"];
	self.dataFilePath = path;
	
	NSFileManager *fileManager = [NSFileManager defaultManager];
	if ([fileManager fileExistsAtPath:path]) {
		NSMutableData *theData  = [NSMutableData dataWithContentsOfFile:path];
		NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:theData];
		
		self.settings = [decoder decodeObjectForKey:@"settings"];
		
		[decoder finishDecoding];
		[decoder release];
	} else {
		self.settings = [[Settings alloc] init];
	}	
}

- (void)saveSettings {
	NSMutableData *theData = [NSMutableData data];
	NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:theData];
	
	[encoder encodeObject:settings forKey:@"settings"];
	[encoder finishEncoding];
	
	[theData writeToFile:dataFilePath atomically:YES];
	[encoder release];
}

NSKeyedArchiver、NSKeyedUnarchiver

encodeWithCoder:、initWithCoder:メソッドの引数に登場した、NSCoderクラスのインスタンスになります。
キーを指定してオブジェクトを保存、復元します。

起動時に復元、終了時に保存

ここまでで、オブジェクトをファイルに保存、復元ができるようになりました。
下のようにして、アプリケーションの起動時に復元の処理、終了時に保存の処理を書いておくと便利です。

- (void)applicationDidFinishLaunching:(UIApplication *)application {
	[self loadSettings];
	self.lhour = settings.lhour;
	
	[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackOpaque;
	tabBarController.moreNavigationController.navigationBar.barStyle = UIBarStyleBlackOpaque;
	
	if (settings.orderOfViewControllers) {
		NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:11];
		for (UINavigationController *controller in tabBarController.viewControllers) {
			[dictionary setObject:controller forKey:[[controller.topViewController class] description]];
		}
		NSMutableArray *controllers = [NSMutableArray arrayWithCapacity:11];
		for (NSString *controllerName in settings.orderOfViewControllers) {
			[controllers addObject:[dictionary objectForKey:controllerName]];
		}
		tabBarController.viewControllers = controllers;
	}
	
    [window addSubview:tabBarController.view];
}

- (void)applicationWillTerminate:(UIApplication *)application {
	[self saveSettings];
}