今回の内容
前回は、単純にNSStringのインスタンスをNurseryに保存するアプリを作りました。
今回は、NUCodingを定義したクラスPersonを作成して、そのインスタンスをNurseryに保存してみます。
Personクラスの追加と定義を行う
前回作成したプロジェクトをもとに作業を進めます。
前回のプロジェクトに新しいクラスPersonを追加します。
追加したら、Person.hの内容を次の様に変更します。
#import <Foundation/Foundation.h>
#import <Nursery/Nursery.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject <NUCoding> //NUCodingプロトコルを指定する
@property (nonatomic, nullable) NSString *firstName;
@property (nonatomic, nullable) NSString *lastName;
//データベースファイル内のオブジェクトとメモリ上のオブジェクトを結び付けるオブジェクト
@property (nonatomic, nullable, assign) NUBell *bell;
@end
NS_ASSUME_NONNULL_END
次にPerson.mの内容を次の様に変更します。
#import "Person.h"
@implementation Person
//プロパティのアクセサメソッドのゲッターとセッターを定義するので、インスタンス変数を明示的に生成する
@synthesize firstName, lastName;
//データベース内でのクラス定義を行うメソッドdefineCharacter:on:を、Nurseryが自動的に呼び出す様にする
+ (BOOL)automaticallyEstablishCharacter
{
return YES;
}
//データベース内でのクラス定義を行う
+ (void)defineCharacter:(NUCharacter *)aCharacter on:(NUGarden *)aGarden
{
[aCharacter addOOPIvarWithName:@"firstName"]; //OOP(オブジェクト)型のインスタンス変数としてファーストネームを追加する
[aCharacter addOOPIvarWithName:@"lastName"]; //OOP(オブジェクト)型のインスタンス変数としてラストネームを追加する
}
//メモリ上のオブジェクトのインスタンス変数をデータベースに保存するための処理
- (void)encodeWithAliaser:(NUAliaser *)anAliaser
{
[anAliaser encodeObject:firstName forKey:@"firstName"];
[anAliaser encodeObject:lastName forKey:@"lastName"];
}
//データベース内のオブジェクトからメモリ上のオブジェクトにインスタンス変数を読み込む処理
- (instancetype)initWithAliaser:(NUAliaser *)anAliaser
{
//この時点では、実際のオブジェクトではなく、それと結び付けられたNUBellのインスタンスを読み込む
firstName = [anAliaser decodeObjectForKey:@"firstName"];
lastName = [anAliaser decodeObjectForKey:@"lastName"];
return self;
}
- (NSString *)firstName
{
//実際に必要になった時点で、ファーストネームを取得して返す
return NUGetIvar(&firstName);
}
- (void)setFirstName:(NSString *)aFirstName
{
NUSetIvar(&firstName, aFirstName); //ファーストネームを設定する
}
- (NSString *)lastName
{
//実際に必要になった時点で、ラストネームを取得して返す
return NUGetIvar(&lastName);
}
- (void)setLastName:(NSString *)aLastName
{
NUSetIvar(&lastName, aLastName); //ラストネームを設定する
}
@end
以上で、Personクラスでの作業は終わりです。
ファイルmain.mを変更する
ファイルmain.mに次の二つの関数を追加します。
void savePerson(void)
{
NSString *aFilepath = [NSHomeDirectory() stringByAppendingPathComponent:@"My-First-Nursery"]; //データベースを保存するファイルパスを取得
NUMainBranchNursery *aNursery = [NUMainBranchNursery nurseryWithContentsOfFile:aFilepath]; //ファイルパスを指定してデータベースオブジェクトを作成
NUGarden *aGarden = [aNursery makeGarden]; //データベースに保存するオブジェクトを管理するオブジェクトを作成
Person *aPerson = [Person new]; //データベースに保存するオブジェクトを作成
[aPerson setFirstName:@"first name a"]; //ファーストネームを設定
[aPerson setLastName:@"last name a"]; //ラストネームを設定
[aGarden setRoot:aPerson]; //データベースに保存するルートオブジェクトにPersonのインスタンスを設定
[aGarden farmOut]; //メモリ上のオブジェクトをデータベースファイルに保存
}
void loadPerson(void)
{
NSString *aFilepath = [NSHomeDirectory() stringByAppendingPathComponent:@"My-First-Nursery"]; //データベースを保存するファイルパスを取得
NUMainBranchNursery *aNursery = [NUMainBranchNursery nurseryWithContentsOfFile:aFilepath]; //ファイルパスを指定してデータベースオブジェクトを作成
NUGarden *aGarden = [aNursery makeGarden]; //データベースに保存するオブジェクトを管理するオブジェクトを作成
Person *aPerson = [aGarden root]; //データベースに保存されたrootオブジェクトを取得
NSLog(@"first name: %@", [aPerson firstName]); //Personのファーストネームをログ出力 first name: first name a
NSLog(@"last name: %@", [aPerson lastName]); //Personのラストネームをログ出力 last name: last name a
}
次にmain関数を次の様に変更します。
int main(int argc, const char * argv[]) {
@autoreleasepool {
savePerson();
}
return NSApplicationMain(argc, argv);
}
変更が終わったら、アプリをビルドして実行します。
実行されたら一旦アプリを終了し、次の様にmain関数を変更します。
int main(int argc, const char * argv[]) {
@autoreleasepool {
loadPerson(); //Personのインスタンスを読み込む
}
return NSApplicationMain(argc, argv);
}
変更後、もう一度アプリをビルドして実行します。
そうするとコンソールに、以下の様に出力されます。
first name: first name a
last name: last name a
Nurseryへ保存したオブジェクトを変更して、再び保存する
先ほど定義したPersonクラスの定義では、実は、一度Nurseryに保存した後にメモリ上のPersonオブジェクトを変更して、Nurseryに再保存しても、データベース内のオブジェクトには、その変更が反映されません。
メモリ上での変更をデータベースファイル内のオブジェクトに反映させるためには、Nurseryに対してどのオブジェクトが変更されたかを教えてあげてから、保存を行う必要があります。
そのためには、オブジェクトが所属するNUMainBranchGardenのインスタンスのmarkChangedObject:メソッドの引数に、変更されたオブジェクトを指定します。
例えば、前回の記事では、イミュータブルなNSStringのインスタンスをNurseryに保存しましたが、これがもしNSMutableString等のミュータブルなオブジェクトの場合は、そのオブジェクトを変更後に、次の様にして、Nurseryにオブジェクトが変更されたことを教えます。
[aGarden markChangedObject:aString];
NUCodingプロトコルを実装するクラスのインスタンについては、
そのインスタンスが初めてNurseryに保存された時と、インスタンスがNurseryから実際に読み込まれた時に、そのインスタンスのインスタンス変数bellにNUBellのインスタンスが設定されるので、インスタンスが変更された時に、次の様にして、Nurseryにインスタンスが変更されたことを教えます。
[[self bell] markChanged];
Personクラスの定義を変更する
Nurseryへの保存時に、データベース内のオブジェクトに変更が反映される様に、Personクラスの定義を変更します。
次の様にPersonクラスのアクセサメソッドのセッターの定義を変更します。
- (void)setFirstName:(NSString *)aFirstName
{
NUSetIvar(&firstName, aFirstName); //ファーストネームを設定する
// Personオブジェクトが変更されたことをNurseryに知らせる
[[self bell] markChanged];
}
- (void)setLastName:(NSString *)aLastName
{
NUSetIvar(&lastName, aLastName); //ラストネームを設定する
// Personオブジェクトが変更されたことをNurseryに知らせる
[[self bell] markChanged];
}
ファイルmain.mを変更する
次の関数をファイルmain.mに追加します
// 前回保存したPersonオブジェクトをメモリに読み込み、変更して、保存する
void changeAndSavePerson(void)
{
NSString *aFilepath = [NSHomeDirectory() stringByAppendingPathComponent:@"My-First-Nursery"]; //データベースを保存するファイルパスを取得
NUMainBranchNursery *aNursery = [NUMainBranchNursery nurseryWithContentsOfFile:aFilepath]; //ファイルパスを指定してデータベースオブジェクトを作成
NUGarden *aGarden = [aNursery makeGarden]; //データベースに保存するオブジェクトを管理するオブジェクトを作成
Person *aPerson = [aGarden root]; //データベースに保存されたrootオブジェクトを取得
[aPerson setFirstName:@"a first name b"]; //ファーストネームを変更
[aPerson setLastName:@"a last name b"]; //ラストネームを変更
[aGarden farmOut]; //メモリ上で変更したオブジェクトをデータベースファイルに反映
}
main関数を次の様に変更します。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 前回保存したPersonオブジェクトをメモリに読み込み、変更して、保存する
changeAndSavePerson();
}
return NSApplicationMain(argc, argv);
}
アプリをビルドして実行します。
実行されたら一旦アプリを終了させます。
次にmain.mファイルを次の様に変更します。
int main(int argc, const char * argv[]) {
@autoreleasepool {
loadPerson(); //Personのインスタンスを読み込む
}
return NSApplicationMain(argc, argv);
}
変更したらアプリをビルドし実行します。
実行後コンソールに次の様に変更された値が出力されます。
first name: a first name b
last name: a last name b
次回
次回は一つのNUMainBranchNurseryのインスタンスを複数のNUMainBranchGardenのインスタンスから利用する方法を説明する予定です。