C++ リハビリ

このところ、Objective-C ガラミの記事を書いていたせいか、他の言語のこと忘れちょる。

C++ リハビリ

ネットに落ちている簡単なレクチャー読んでリハビリ。

こういう時に江添本とかはよくない。

一週間で身につくC++ 言語の基本』シリーズ

こういうの、いいねー。

メモ

本当にメモ。

ostream 基本

Objective-C のわかりにくいところ

まだ、完全に理解していないところ。

・@property と @synthesize

なお、@synchronized は排他制御に関する事柄なので、全然別の概念。

(→まずまず理解)

・NSNotificationCenter 関係

これは、ワイが、というより、初学者すべて。
難しく説明しすぎのような? オブジェクト間で通知したい時に使う。
あるクラスが通知を受け取りたい時、その仕組みが必要なので、イニシャライザに NSNotificationCenter も組み込んでおく。
のような感じでいいのでは?

 

 

CoreData 再び

以前に『Objective-C 再入門 -1.0.2- coreData を使う』という記事を書いた。

そこでは、余計なことを気にしないようにコマンドラインツールで CoreData を使ったんだが、実務的にはこんな使い方はしない。大抵の場合、iOS プロジェクトで GUI 環境で使う場合が多いと思う。

そういうわけで、今回は GUI 環境で使うときの覚え書き。

諸々の事情で MacOS だけどさ。

最も簡単な Core Data サンプル

デフォルトに Person クラス(プロパティは name )を追加して AppDelegate の applicationDidFinishLaunching メソッドに以下のコードを実装。


   _persistentContainer = [self persistentContainer];
    NSManagedObjectContext *context = _persistentContainer.viewContext;
    Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person"
                                                     inManagedObjectContext:context];
    person.name = @"taro";
    NSError *error = nil;
    [context save:&error];

これで、永続化されている。

モデルからクラスを自動生成する場合、元になるクラスのコーディングは不要

見出しの通り。

モデルで Person というクラスを想定した場合、自動生成した時、Person+XXX というファイルが4つほどできるが、元の Person というクラスをあらかじめコーディングしておくのは不要

というのは、現在の Xcode (Ver 14)とは動作が違うのか @property などでせこせこ Person クラスを作成してから、自動生成させる、みたいに書いてある記事があって、???となっていたから。

確かに Java の hibernate では、クラスのコーディングはコーダーの仕事だが、CoreData では xxx.xcdatamodeled 上でクラスを定義するので、そこから考えても不要。

なんか、おかしな記事だった。

今回は、One To と Many To も定義したが、自動生成すると、例えば、One To Many の One 側のクラスは

となる。

Many の側として Child クラスを想定したが、Person からしたら Child のインスタンスは複数取りえるのでアクセサの返値の型は

NSSet<Child *>

になるわけですね。

その記事、その説明もなんかおかしかったな。

CoreData に限らず、ORM の使い方をなにか誤解している人は多いので、リアルでもこういう人への対処は注意ですね。

あと、relationship の名前が、そのままメンバー変数の名前になる(こういう仕様は使ってみないとわからない)ので、モデル上での作業がかなり重要ということがわかります。

.xcdatamodeled でのクラス定義

話を戻して。

今回は、クラスを二つ用意して、その間に One To と Many To の関係性(relationship)を入れます。

わかってしまえば、作業は少ない。

One 側のクラス Person の定義は↓。

 

Many 側のクラス Child の定義は↓。

これらを定義したら、いつものように

menu -> Editor -> Create NSManagedObject subclass…

で、クラスを自動生成させます。

機能は何もありませんが、ビルドするだけなら、この時点でランさせてもエラーは出ないはずです。

問題となるのは、実際に永続化するときでしょう。

また、循環参照の問題とか起こる・・・のか?

永続化

もうちょっと上手い書き方もあるような気もするのだが、永続化自体はできた。
context を用意して


    Child *child = [NSEntityDescription insertNewObjectForEntityForName:@"Child"
                                                   inManagedObjectContext:context];
    child.name = @"taro";
    
    Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person"
                                                   inManagedObjectContext:context];
    
    person.name = @"akiba";

    [person addChildrenObject:child];

として、CoreData が生成してくれたメソッド addChildrenObject で person に child を追加する。

save とすると、

と sqlite に永続化できた。

この時、ZCHILD_ZPERSON_INDEX に関係性の情報が格納されている、ということなんだろう。

しかし、循環参照は起きなかったが、どこで context を生成するとか、もうちょっと経験を積む必要がありそう。

永続化実体の sqlite ファイルの場所

ところで、永続化したとき、この情報を永続化している sqlite ファイルはデフォルトではどこにあるのだろうか?

実は、最初どこにあるのか不明で本当に永続化できているか確認のしようがなかった。

あたりをつけて探したら見つかったんだが、わかりにくいぞ(笑)。

アプリ自体の名前が CoreDataTest として、
bundle indentifier が com.sample.CoreDataTest2
モデルの名前が CoreDataTestDB
ユーザーの名前が hoge さんだったとすると

/Users/hoge/Library/Containers/CoreDataTest2/Data/Library/Application Support/CoreDataTest/CoreDataTestDB.sqllite

になる。長っ。

ココアアプリの場合、デフォで作成される persistentContainer をそのまま流用すれば、このファイルは自動で作ってくれる。説明が前後するが、前回のコマンドラインツールの場合は、(古い書き方のせいで)自分で作る必要があった。

fetchRequest

永続化はできたので、今度はこれら情報を取り出したい。

persistentContainer を定義した同一クラスで情報を取り出す適当なメソッドを作成し以下のように実装した。


    NSManagedObjectContext *context = self.persistentContainer.viewContext;//or newBackgroundContext
 
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([Child class])];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];

    NSArray *results = [context executeFetchRequest:fetchRequest error:nil];

    NSUInteger i = 0;
    for (Child *data in results) {
        //do something.
        NSLog(@"data: %lu name: %@", (unsigned long)i, data.name);
        i++;
    }

このメソッドを呼び出す(一番簡単には menu item のアクション実行先をこのメソッドにする)。

Target Output には、Child クラスの name が永続化された個数分、表示される。

できてますね。

Child クラスは Person クラスと One To Many の関係があるので

NSLog(@"data: %lu name: %@ Person name: %@", (unsigned long)i, data.name, data.person.name)

とすると Person の name も表示されます。

なんか ORM っぽくなってきました。

Person を読んでから、関係性の情報を使って Child の name を取得・表示する。


    for (Person *data in results) {
        //do something.
        NSLog(@"data: %lu Person name: %@", (unsigned long)i, data.name);
        NSArray *children = [data.children allObjects];
        for(Child *child in children){
            NSLog(@"   Child name: %@", child.name);
        }
        i++;
    }

NSSet で返されるので、一括して NSArray に変換しておくと後の処理が楽でしょうか。

NSTableView を組み込む

Xcode 上での TableView の操作に関しては、(Swift & iOS になってしまうが)

CoreData と TableView の使い方

が参考になるでしょうか。

Tips

本題とは直接関係ないが、気になったことなど。

レガシーコード

レガシーなプロジェクトでは NSPersistentContainer を使わないようだ。

こちらの 「iOS10 より前の・・・」項など参照。

sqlite ファイルを削除するときは思い切りよく

いまいち仕組みがわかってないのだが、XXX.sqlite を動作させると XXX.sqlite-shm と XXX.sqlite-wal というファイルも生成される。

通常使用なら気にしなくていいのだが、元の XXX.sqlite を削除した場合、ここで使っていた情報は生成された二つのファイルにも書き込まれているので、これらも削除しないとアプリが新規 sqlite ファイルをオープンできずエラーが出る。

テストの時にうっかりやってしまうと「え? ワイ何か重大なミス犯したかな」と一瞬固まるので(笑)、こういう場合は思い切りよく削除しましょう。
テスト環境だし。

予定

CRUD 操作・・あたり。

(続く)

 

Xcode で GUI アプリ 4

このシリーズも今回4回目。

継続していて気が付いたことは、アプリの製作は文法の学習とはまた異なった知識が必要だということ。

前回扱った内容はまさにそれで、メニューイベントを管理するクラスをどこに置くか?という話題で、ある程度凝ったアプリならば、NSWindowController の派生クラスに置くのがいいのでは、みたいな話をした。

答えが複数あり、状況によって最適解を探す、というのは、プログラミングに限らず、習得するのが難しい領域の一つなんでしょうね。

前置きはともかく、今回は、特定のクラスが必要になったとき、それを Objective-C でどう構成するかという話。

イニシャライザでインスタンスを返す

どう構成するかはひとまず置いて、定型的な書き方の復習。


@interface Hoge : NSObject
-(nullable instancetype) initHoge;
@end

@implementation Hoge
-(nullable instancetype) initHoge;
{
    self = [super init];
    if(self)
      {
         //初期化処理
       }
   return self;
}
@end

この書き方、昔は無茶苦茶違和感あったけど、最近慣れたな。

なお、initXxx は init family とかイニシャライザなどと言われ、クラスの初期化を受け持つメソッド。
init 以下に適当な文字(列)をつけるが、最初の文字は大文字にする必要がある。

だから、initialize は init familiy ではない

以前に他人のコードを読んでいたとき、同一クラス内で init と initialize が同居していて「は?」と思ったのだが、そういうことらしい。

(続く)