Persistence(5)~アーカイブへの保存(1)

2011. 02. 09
2つの目のアプリケーションデータの保存は、アーカイブを使用する方法です。

アーカイブに関しては、過去に『Foundation(アーカイブ)』や『Nav(17)~Detail Edit(2)』で触れていますので詳しくはそちらを参照していただくとして、一言で言うと複数のオブジェクトを(その相互関係も含めて)バイト列(バイナリファイル)に変換したものです。

アーカイブ化は、プロパティリストより使用できるオブジェクトの型の自由度が高く、NSCodingプロトコルに準拠しているFoundationとCocoaTouchのほとんどのクラスのオブジェクトが利用できます。
(NSCodingプロトコルに適合しているかどうかは、クラスリファレンスの冒頭にある『Conforms to』欄にNSCodingが含まれているかどうかで判別できます)

今回は前回作成したPersistenceプロジェクトを、プロパティリストからアーカイブを使用するように改造するので、必要があればプロジェクトフォルダごと別のフォルダに退避してください。


●FourLinesクラスの作成

Persistenceアプリケーションのユーザデータである4つのテキストフィールドの値をアーカイブとして保持するため、FourLinesという新しいクラスを作成します。

XcodeのClassesを右クリックし、『追加』→『新規ファイル...』でObjective-C classテンプレート(Subclass ofはNSObject)を選択し、ファイル名をFourLinesとして作成します。

1205

1206


●アーカイブ化を行うクラスのヘッダファイルFourLines.hの編集

アーカイブ化を行うFourLinesクラスは、NSCodingプロトコルへの準拠が必須となります。

Nav(18)~Detail Edit(3)』では、データ編集を途中でキャンセルした場合の対処として、プロパティリストPresidents.plistの一部を一時的に保持する可変辞書を作成していましたが、今回はアーカイブ化するクラスのインスタンスをまるごとコピーして簡単に行えるよう、NSCopyingプロトコルにも準拠させます。

ただしNSCopyingプロトコルへ準拠はさせますが、実際にコピーを利用した処理は今回実装していません。
(太字が追加した部分)

#import <Foundation/Foundation.h>

#define kField1Key @"Field1"
#define kField2Key @"Field2"
#define kField3Key @"Field3"
#define kField4Key @"Field4"


@interface FourLines : NSObject <NSCoding, NSCopying> {
    NSString *field1;
    NSString *field2;
    NSString *field3;
    NSString *field4;

}

@property (nonatomic, retain) NSString *field1;
@property (nonatomic, retain) NSString *field2;
@property (nonatomic, retain) NSString *field3;
@property (nonatomic, retain) NSString *field4;


@end

1207

本書やサンプルコードではUIKitをインポートしていますが、NSObjectのテンプレートであればFoundationをインポートしているはずです。

FourLinesクラスで扱うクラスはNSObject、NSCoding、NSCopyingクラスなので、Foundationのままで問題無いのでそのままにしておきます。

最初にテキストフィールドの値をアーカイブ化するにあたり、そのための各テキストフィールド用のキーを#defineで設定しています。

旧版の本書では#importの前に記述していますが、ここでは一般的な#importと@interfaceの間に記述しています。

iOS 4.x対応の最新版のサンプルコードではヘッダファイルではなくソースファイルに記述していますが、(3.x用と異なりGoogle Previewも無いので)理由が分からないので取り敢えずヘッダファイルのままにしておきます。

@interfaceのクラス宣言では、アーカイブ化のためのNSCodingプロトコルと、アーカイブデータをコピーするためのNSCopyingプロトコルへの準拠を追加しています。

後はアーカイブに保存する4つのテキストフィールド用文字列のプロパティを宣言します。


●アーカイブ化を行うクラスのソースファイルFourLines.mの編集

ソースファイルでは4つのプロパティの実装と、NSCodingプロトコルの必須メソッド2つ、そしてコピーを行うためのNSCopyingプロトコルの必須メソッドを記述します。
(太字が追加した部分)

#import "FourLines.h"

@implementation FourLines

@synthesize field1;
@synthesize field2;
@synthesize field3;
@synthesize field4;

#pragma mark NSCoding

- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:field1 forKey:kField1Key];
    [encoder encodeObject:field2 forKey:kField2Key];
    [encoder encodeObject:field3 forKey:kField3Key];
    [encoder encodeObject:field4 forKey:kField4Key];
}

- (id)initWithCoder:(NSCoder *)decoder {
    if (self = [super init]) {
        self.field1 = [decoder decodeObjectForKey:kField1Key];
        self.field2 = [decoder decodeObjectForKey:kField2Key];
        self.field3 = [decoder decodeObjectForKey:kField3Key];
        self.field4 = [decoder decodeObjectForKey:kField4Key];
    }
    return self;
}

#pragma mark -
#pragma mark NSCopying

- (id)copyWithZone:(NSZone *)zone {
    FourLines *copy = [[[self class] allocWithZone:zone] init];
    field1 = [self.field1 copy];
    field2 = [self.field2 copy];
    field3 = [self.field3 copy];
    field4 = [self.field4 copy];
    return copy;
}


@end

1208

1)4つのプロパティの実装

アーカイブ化する4つのテキストフィールドのプロパティを実装します。

2)encodeWithCoder:の実装

encodeWithCoder:はNSCodingプロトコルの必須メソッドの1つで、データのアーカイブ化(エンコード)を行います。

ヘッダファイルの#defineで定義したキーを使用し、4つのテキストフィールドのNSStringオブジェクトをencodeObject:forKey:メソッドでエンコードしています。

3)initWithCoder:の実装

initWithCoder:もNSCodingプロトコルの必須メソッドで、データのアンアーカイブ化(デコード)を行います。

最初にスーパークラスによる初期化をし、初期化が失敗した場合はデコードを行わないように判定処理をしています。

FourLinesクラスは、NSCodingプロトコルに準拠していないNSObjectを親クラスとしているので、initWithCoder:ではなくinitメソッドで初期化を行っています。

デコードは4つのテキストフィールドのデータに対し、ヘッダファイルの#defineで定義したキーを使用してdecodeObjectForKey:メソッドで行っています。

4)copyWithZone:の実装

copyWithZone:はNSCopyingプロトコルの必須メソッドで、FourLinesクラスのインスタンスのコピーを返します。

最初にclassメソッドでFourLinesクラスのオブジェクトを取得し、allocWithZone:で引数zoneの(つまり呼び出し元と同じ)メモリ領域に割り当て、インスタンスを初期化します。

そこにcopyメソッドで4つのテキストフィールドのデータのコピーを作成し、返します。


copyWithZone:

- (id)copyWithZone:(NSZone *)zone

レシーバのコピーである新しいインスタンスを返します。
(必須)

返されるオブジェクトはセンダーによって暗黙的に保持されるため、解放する責任があります。

受け取るオブジェクトに『可変と不変』が混在している場合、コピーで返されるのは不変になるので適用を考慮してください。

それ以外の場合、コピーはクラスで決定されている正確な性質になります。

zone:新しいインスタンスを割り当てるメモリ領域を識別するゾーンを指定します。
zoneがNULLの場合、新しいインスタンスはデフォルトゾーンに割り当てられ、関数からはNSDefaultMallocZoneが返されます。


allocWithZone:

+ (id)allocWithZone:(NSZone *)zone

指定したゾーンに新しいインスタンスのためのメモリの場所を割り当て、レシーバのクラスの新しいインスタンスを返します。

新しいインスタンスのisaインスタンス変数は、クラスを表すデータ構造体で初期化され、その他のインスタンス変数は0に設定されます。

zoneがnilの場合、新しいインスタンスは(NSDefaultMallocZoneによって返される)デフォルトのゾーンに割り当てられます。

初期化工程を完了するには、以下のようにinit~メソッドを使用する必要があります。

TheClass *newObject = [[TheClass allocWithZone:someZone] init];

サブクラスで任意の初期化コードを含めてallocWithZone:をオーバーライドしないでください。

代わりにその目的に沿ったクラスの指定するinit~メソッドを実装してください。

あるオブジェクトが別のオブジェクトを生成する場合、同じメモリ領域から双方が割り当てられているか確認することを推奨します。

NSObjectプロトコルで宣言されている)zoneメソッドはこの目的に使用することができ、以下のようにレシーバの配置されているゾーンの場所を返します。

id myCompanion = [[TheClass allocWithZone:[self zone]] init];

(ガベージコレクションではない)メモリ管理を使用している場合、このメソッドは返す前に保持します。

返されたオブジェクトのretainカウントは1になり、自動解放されません。

このメソッドを呼び出した側は、releaseautoreleaseで返されたオブジェクトを解放する責任があります。

zone:新しいインスタンスを生成するメモリ領域を指定します。


●サンプルコードとの差異

旧版の本書とiOS 4.x対応の最新版のサンプルコード、さらに3.x用で差異があります。

・initWithCoder:の違い

initWithCoder:メソッドにおいて、iOS 4.x版ではオブジェクトの保持の仕方が異なっています。
(太字が異なる部分)

- (id)initWithCoder:(NSCoder *)decoder {
    if (self = [super init]) {
        field1 = [[decoder decodeObjectForKey:kField1Key] retain];
        field2 = [[decoder decodeObjectForKey:kField2Key] retain];
        field3 = [[decoder decodeObjectForKey:kField3Key] retain];
        field4 = [[decoder decodeObjectForKey:kField4Key] retain];
    }
    return self;
}

旧版の本書とiPhone OS 3.x版のサンプルコードではselfにテキストフィールドのプロパティで展開したオブジェクトを入れているのに対し、iOS 4.x版のサンプルコードではテキストフィールドのインスタンス変数にオブジェクトを入れてretainを施しています。

この違いがどういう意味か分からなかったのですが、kenmazさんの『kenmazのはてな/Obj-C再入門・今日のハマり所 initWithCoder内ではretainし忘れに注意』で丁寧な解説がされていました。

どちらもdecodeObjectForKey:メソッドでアンアーカイブしたオブジェクトをテキストフィールドのインスタンス変数に設定する点は変わりません。

ただinitWithCoder:で設定した値はメソッドを抜ける際に破棄されるので保持する必要があり、保持する方法としてプロパティの属性を利用するか、設定時にretainをかけておくかの違いとなっています。

kenmazさんの言う『コンストラクタの中でセッタを記述するのに違和感がある』という理由であれば、後述するcopyWithZone:でセッタを使う記述に書き換えていることと矛盾するような感じもしますので、書き換えた理由はよく分かりません。

・copyWithZone:の違い

copyWithZone:メソッドにおいて、iPhone OS 3.x版とiOS 4.x版ではコピーのゾーン指定が異なります。

データ設定時にデコードしたオブジェクトを保持しています。
(太字が異なる部分)

- (id)copyWithZone:(NSZone *)zone {
    FourLines *copy = [[[self class] allocWithZone: zone] init];
    copy.field1 = [[self.field1 copyWithZone:zone] autorelease];
    copy.field2 = [[self.field2 copyWithZone:zone] autorelease];
    copy.field3 = [[self.field3 copyWithZone:zone] autorelease];
    copy.field4 = [[self.field4 copyWithZone:zone] autorelease];
    return copy;
}

copyWithZone:では、copyメソッドではなくcopyWithZone:メソッドによるコピーに変更されています。

これは呼び出し元と同じメモリ領域にコピーを置きたいためだと推測できます。

旧版の本書では単純にテキストフィールドのインスタンス変数に代入していましたが、3.x以降のサンプルコードではインスタンスcopyにセッタで代入し、セッタによるretainを相殺するためかautoreleaseが追加されています。

copyWithZone:は3.x用でも変更されているため、initWithCoder:の変更との関連性は無く、この2カ所以外(PersistenceViewControllerの改造箇所も含めて)差異はありません。

手掛かりが少なく調査に時間がかかりそうなので、保留にします。



参考文献

NSCopying Protocol Reference

NSObject Class Reference

kenmazのはてな/Obj-C再入門・今日のハマり所 initWithCoder内ではretainし忘れに注意

Wikipedia/コンストラクタ

詳解 Objective-C 2.0 第3版詳解 Objective-C 2.0 第3版
(2011/12/28)
荻原 剛志

商品詳細を見る

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る






5.1chホームシアターシステム
0 Comments
Leave a comment
管理者にだけ表示を許可する
Top
0 Trackbacks
Top
Calendar
07 | 2017/08 | 09
Sun Mon Tue Wed Thu Fri Sat
- - 1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31 - -
Recent Articles
iTunes


Swift
Categories
Tips
Profile

水月杏香

Author:水月杏香
永遠の初心者プログラマ。

Wish List
WACOM


ARC
Technical Q&A
情報プロパティリストキー
Start Developing iOS Apps Today
BOSE

5.1chホームシアターシステム
Reference
NSApplicationDelegateプロトコル
NSArrayクラス
NSAutoreleasePoolクラス
NSBundleクラス
NSBundle UIKit追加分
NSCalendarクラス
NSCoderクラス
NSCodingプロトコル
NSCopyingプロトコル
NSDataクラス
NSDateクラス
NSDateFormatterクラス
NSDictionaryクラス
NSEntityDescriptionクラス
NSEnumeratorクラス
NSErrorクラス
NSExceptionクラス
NSFetchRequestクラス
NSFileHandleクラス
NSFileManagerクラス
NSIndexPathクラス
NSIndexPath UIKit追加分
NSKeyedArchiverクラス
NSKeyedUnarchiverクラス
NSKeyValueCodingプロトコル
NSLocaleクラス
NSManagedObjectクラス
NSManagedObjectContextクラス
NSManagedObjectModelクラス
NSMutableArrayクラス
NSMutableCopyingプロトコル
NSMutableDictionaryクラス
NSMutableSetクラス
NSNotificationクラス
NSNotificationCenterクラス
NSNullクラス
NSNumberクラス
NSObjectクラス
NSObject UIKit追加分
NSObjectプロトコル
NSPersistentStoreクラス
NSPersistentStoreCoordinatorクラス
NSPredicateクラス
NSPropertyListSerializationクラス
NSRunLoopクラス
NSSetクラス
NSStringクラス
NSString UIKit追加分
NSTimerクラス
NSTimeZoneクラス
NSURLクラス
NSURLProtectionSpaceクラス
NSURLRequestクラス
NSUserDefaultsクラス
NSValueクラス

UIActionSheetクラス
UIActionSheetDelegateプロトコル
UIActivityIndicatorViewクラス
UIAlertViewクラス
UIAlertViewDelegateプロトコル
UIApplicationクラス
UIApplicationDelegateプロトコル
UIBarButtonItemクラス
UIBarItemクラス
UIButtonクラス
UIColorクラス
UIControlクラス
UIDatePickerクラス
UIDeviceクラス
UIEventクラス
UIFontクラス
UIGestureRecognizerクラス
UIImageクラス
UIImageViewクラス
UIKit Function
UILabelクラス
UINavigationControllerクラス
UINavigationItemクラス
UIPickerViewクラス
UIPickerViewDataSourceプロトコル
UIPickerViewDelegateプロトコル
UIPinchGestureRecognizerクラス
UIResponderクラス
UIScreenクラス
UIScrollViewクラス
UISearchBarクラス
UISearchBarDelegateプロトコル
UISegmentedControlクラス
UISliderクラス
UISwipeGestureRecognizerクラス
UISwitchクラス
UITableViewクラス
UITableViewCellクラス
UITableViewControllerクラス
UITableViewDataSourceプロトコル
UITableViewDelegateプロトコル
UITapGestureRecognizerクラス
UITextFieldクラス
UITextFieldDelegateプロトコル
UITextInputTraitsプロトコル
UITextViewクラス
UITextViewDelegateプロトコル
UIToolbarクラス
UITouchクラス
UIViewクラス
UIViewControllerクラス
UIWebViewクラス
UIWebViewDelegateプロトコル
UIWindowクラス

AVAudioPlayerクラス
AVAudioPlayerDelegateプロトコル

CADisplayLinkクラス
CAEAGLLayerクラス
CALayerクラス

CGAffineTransform
CGBitmapContext
CGColor
CGColorSpace
CGContext
CGGeometry
CGImage
CGPath

EAGLContextクラス
EAGLDrawableプロトコル

Foundation Constants
Foundation Data Types
Foundation Functions

MPMediaItemクラス
MPMediaItemArtworkクラス
MPMediaPlaylistクラス
MPMediaPropertyPredicateクラス
MPMediaQueryクラス
MPMusicPlayerControllerクラス

Randomization Services

System Sound Services
Amazon


OpenGL ES
SQLite
Monthly Archives
Recent Comments
Recent TrackBacks
RSS Link
Visitors
QR Code
QR