NSHTTPCookieStorage 互換クラスの自前実装の最後。残っていたクッキーのファイルへの保存と読み取りの実装に取りかかる。
まずは書き出しから。plist 形式でクッキーの内容を保存する。
XCHTTPCookieStorage.m
- (void)_writeCookies:(NSTimer*)timer
{
if (!_is_modified) {
return;
}
NSMutableArray* write_cookies = [NSMutableArray array];
for (NSHTTPCookie* cookie in [self cookies]) {
if ([self _isWritableCookie:cookie]) {
[write_cookies addObject:[cookie properties]];
}
}
BOOL result = [write_cookies writeToFile:[self _filepath] atomically:YES];
if (result) {
_is_modified = NO;
} else {
NSLog(@"Failed to write : %@", [self _filepath]);
}
}
_is_modified はメモリ上のクッキーに追加や変更が入った場合に YESになる。-[cookies] でメモリ上に保持しているクッキーを取得し保存対象かどうかを -[_isWritableCookie:] でチェックし、最後に -[NSArray writeToFile:atomically:] で保存する。atomically:YES とすると一旦テンポラリファイルへ書き出した後、目的のファイルへコピー(上書き)する。これは Safari のクッキー調査で見た動作と同じ。
なお単一アプリからの利用しか想定していないので排他制御(ロック)は行っていない。NSTimerからの呼出しも同一スレッドなのでアプリ内でのファイル書き出し競合も通常は起らない。
保存対象かどうかの判断を行うメソッド。
- (BOOL)_isWritableCookie:(NSHTTPCookie*)cookie
{
if ([cookie isSessionOnly]) {
return NO;
}
NSDate* cookie_expires_date = [cookie expiresDate];
NSDate* date = [NSDate date];
if (cookie_expires_date &&
[date compare:cookie_expires_date] == NSOrderedDescending) {
return NO;
}
return YES;
}
セッション限りのもの、期限切れのものは保存しない。
クッキーファイルの保存場所を決定するメソッド。
- (NSString*)_filepath
{
NSString* path = [NSString stringWithFormat:@"%@/%@", [NSSearchPathForDirectoriesInDomains(NSAllLibrariesDirectory, NSUserDomainMask, YES) objectAtIndex:0], @"Cookies"];
NSError* error;
NSFileManager* fm = [NSFileManager defaultManager];
if (![fm isReadableFileAtPath:path]) {
[fm createDirectoryAtPath:path
withIntermediateDirectories:YES
attributes:nil
error:&error
];
if (error) {
NSLog(@"%@", error);
}
}
NSString* identifier = [[NSBundle mainBundle] bundleIdentifier];
NSString* filepath =
[NSString stringWithFormat:@"%@/%@.plist", path, identifier];
return filepath;
}
保存先は ~/Library/Cookies、ファイル名はアプリケーションの Identifier + ".plist" とする。保存先のフォルダが存在しない場合は作成する。
最後に -[_writeCookies:] を NSTimerに登録し、30秒毎に(非同期で)保存する。
#define SYNC_INTERVAL 30
- (id)init
{
if (self = [super init]) {
:
_timer=[[NSTimer scheduledTimerWithTimeInterval:SYNC_INTERVAL
target:self
selector:@selector(_writeCookies:)
userInfo:nil repeats:YES] retain];
:
}
これで書き出しの実装ができた。
次は読み込み。これは既存のメソッドの組み合わせで簡単にできる。
- (void)_readCookies
{
NSArray* read_cookies =
[NSArray arrayWithContentsOfFile:[self _filepath]];
if (read_cookies) {
for (NSDictionary* properties in read_cookies) {
[self setCookie:[NSHTTPCookie cookieWithProperties:properties]];
}
}
}
さて実行してみよう。
クッキーを送出するサイトを開き少し(30秒)待つとファイルが生成された。
中身をみるとクッキーが書き出されているようだ。
試しにログイン状態でアプリケーションを一旦終了し、再起動してみる。
ページを開くとログイン状態が保たれていた。ファイルへ保存したクッキーがメモリへ読み込まれきちんと送出されている。
最後にアプリ終了時にクッキーを保存するため NSApplicationWillTerminateNotification の受取を登録しておく。
- (id)init
{
if (self = [super init]) {
:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_willTerminate:)
name:NSApplicationWillTerminateNotification
object:nil];
:
}
アプリ終了直前に NSApplicationWillTerminateNotification がポストされたら下記のメソッドを呼出してクッキーを保存する。
- (void)_willTerminate:(NSNotification*)notification
{
[self _writeCookies:nil];
}
ソースコード:CookieStorage-5.zip
- - - -
ようやくクッキーのハンドリングが一段落できた(長かった)。