作業手順
前作の第1案にBindingsを加えて第2案を作ります。前作のフォルダーをコピーして新しいフォルダーを作り、新しい方を加工するのが楽でしょう。私は第1案のフォルダーをMyCoreData、第2案のフォルダーをMyCoreData_2としました。
Entityの設定、ViewController.h、AppDelegateのhとmはすべて共通で、作業はストーリーボードの設定とViewController.mの変更だけです。
ストーリーボードの設定
まず第1案で設定した3本のコネクションをすぺて外します。TableViewのIBOutlet、それにaddとremoveからのIBActionですね。それぞれを選択し、右側のパネルのConnectionでxみたいな印をクリックします。アシスタントエディタを開く必要はありませんよね。View Controller Sceneに部品Array Controllerをセットします。
Array Controllerを選択し、右側のパネルのAttributeでPreserve Selectionと Select inserted Objectsのチェックを外します。
Object ControllerのModeをEntry nameとし、Entry nameをMonsterにします。Prepares Contentのチェックを外します(多分デフォールト)。Editableはデフォールトでチェックがはいっている筈です。
addボタンからArray Controllerにコントロール・ドラッグします。内訳が示されるのでaddを選択します。同様にremoveボタンからコントロール・ドラッグし、removeを選択します。
Table Column(テーブルビューの内側にある)を選択し、右側のパネルのBindingsのValueでBind toにチェックし、Array Controllerを選択します。Model Key PathをmonsterNameにします。Controller KeyはarrangedObjectsになっている筈です。
ViewController.m
ViewController.mはTableViewにコネクトされていないので、TableView関連のコードはすべて不要になります。あとモンスターの配列は第1案ではmonstaryに格納していましたが、第2案ではArrayControllerがモンスターの配列を管理するので、monstaryも不要です。 例によってViewController.mにある元のコードをすべて削り、代わりに次のコードをこのままペーストして下さい。
//
// ViewController.m
// MyCoreData
//
// Created by Myself on 2022/03/25.
// Copyright © 2022 None. All rights reserved.
//
#import "ViewController.h"
ViewController *viewctrl_ptr = nil; /* (referenced by 'AppDelegate') */
@implementation ViewController
{
// __weak IBOutlet NSTableView *tbl_view_ptr; <--(not used)
IBOutlet NSArrayController *ary_ctrl_ptr;
#pragma mark global variables within ViewController
const NSString *MYENTITY; /* for @"Monster" */
const NSString *THE_NAME; /* for @"monsterName" */
AppDelegate *parent_ptr;
NSManagedObjectModel *my_mom;
NSPersistentStoreCoordinator *my_psc;
NSManagedObjectContext *my_moc;
NSFetchRequest *my_fetch;
// NSMutableArray<NSManagedObject *> *monstary; <--(not used)
// NSInteger nbr_rows; <--(not used)
// BOOL editflag; <--(not used)
}
#pragma mark initializers
- (void)viewDidLoad
{ [super viewDidLoad];
MYENTITY = @"Monster";
THE_NAME = @"monsterName";
viewctrl_ptr = self; /* set public variable */
// tbl_view_ptr.delegate = self;
// tbl_view_ptr.dataSource = self;
// monstary = [[NSMutableArray alloc] init]; /* initialize monster array */
// nbr_rows = 0; /* no row in table-view */
// editflag = NO; /* not editing */
} // viewDidLoad
- (void)setRepresentedObject:(id)representedObject
{ [super setRepresentedObject:representedObject];
// Update the view, if already loaded.
} // setRepresentedObject
#pragma mark public functions
- (void)setParent_ptr:(AppDelegate *)myparent
/* called by 'AppDelegate' */
{ parent_ptr = myparent;
NSLog(@"parent ready");
} // setParent_ptr
-(void)set_pc
/* set persistent-container variables */
/* called by 'AppDelegate' */
/* called after AppDelegate ready */
{ [self initcore]; /* initialize core data */
// [tbl_view_ptr reloadData]; /* display registered Monsters */
} // set_pc
- (void)save_ary
/* save the whole managed object context */
/* called by 'AppDelegate' */
{ NSError *errorptr;
BOOL myresult;
errorptr = nil;
myresult = [my_moc save: &errorptr];
if ( ( myresult == NO ) || ( errorptr != nil ) )
NSLog(@"managed object context save error");
else
NSLog(@"managed object context successfully saved");
} // save_ary
#pragma mark private function
/* (empty) */
#pragma mark event handlers
/* (empty) */
#pragma mark core data functions
- (void)initcore
/* initialize core data */
/* note: 'my_mom' and 'my_psc' not used */
{ NSArray *temp_ary;
NSError *errorptr;
NSAssert(parent_ptr.persistentContainer != nil, @"persistentContainer failed");
my_mom = parent_ptr.persistentContainer.managedObjectModel;
NSAssert(my_mom != nil, @"managed object managedobjectmode failed");
my_psc = parent_ptr.persistentContainer.persistentStoreCoordinator;
NSAssert(my_psc != nil, @"persistent store coordinator failed");
my_moc = parent_ptr.persistentContainer.viewContext;
NSAssert(my_moc != nil, @"managed object context failed");
// array-controller's managed object
// set here
ary_ctrl_ptr.managedObjectContext = my_moc;
my_fetch = [[NSFetchRequest alloc] initWithEntityName: MYENTITY];
errorptr = nil; /* (unnecessary ... result not used) */
temp_ary = [my_moc executeFetchRequest: my_fetch error: &errorptr];
NSAssert(temp_ary != nil, @"initial fetch failed");
if ( temp_ary.count == 0 )
NSLog(@"no monster in sandbox");
else
{ NSLog(@"%d monster(s) in sandbox", (unsigned int)temp_ary.count);
// ArrayController (not monstary) gets monsters
// [monstary addObjectsFromArray: temp_ary];
[ary_ctrl_ptr addObjects: temp_ary];
}
} // initcore
- (void)createOneWithName: (NSString *)new_name
/* create (and save) one entity */
{ // (not used)
} // createOne
#pragma mark delegate and datasource
/* (empty) */
@end
最後にアシスタントエディタを使ってストーリーボードとViewController.mを並べ、Array ControllerのIBOutletを作ります。名前はary_ctrl_ptrにします。
完成
使い勝手は第1案と同じで、addボタンを押すとモンスター名が追加され、removeボタンを押すと選択されたモンスターが削除されます。残念ながらaddボタンを押してもカーソルは表示されず、最下段の空白行をダブルクリックする必要があります。
すでに表示されているモンスター名をダブルクリックして編集すると、編集後の名前が保存されます。何もしていないのに、いつの間にか編集機能が追加されていました(笑)。
BindingsのおかげでViewController.mのコードは大幅に減りました。なおYouTubeでVersluisさんの完全コードレス作品を見ることができます。ただし2014年のものだそうで、多少の手直しが必要のようです。
回顧・道具としてのCoreDataとBindings
アップルの公式文書には「Bindingsによりコードの量は減るけれども、問題が起きたときのデバッグは困難である。Bindingsに手を出す前に、Bindingsを使わない通常のコーディングに十分習熟するよう強く勧める」とあります。これって意訳すれば「初心者はBindingsに手を出すな」ですよね(笑)。
実はCoreDataについてもHillegassさんは同じスタンスのようで、TableViewについて3章にわたり詳しく説明し、「後の章でCoreDataの解説をする。読者が経験豊かなプログラマーになれば、CoreDataを使って簡潔なプログラムを書くことができるけれども、その日まではCoreDataなしでTableViewを使いこなせるよう、コーディングを練習することが望ましい」と書いています。
格言風にいえば「新しい道具を欲しがるより、今の道具をマスターせよ」というところでしょうか。