「やっとわかったCoreData」のモノマネ

## 概要
 
@pe-taさんの「やっとわかったSwift/CoreData入門」をObjective-Cに移植したものです。（逐語訳ではありません。自己流のコードだらけです。)マックブック用ですから、よかったらアップルのDeveloperアカウントを持ってない方も使ってみてください。<br>
バージョンはmacOSが12.3、Xcodeが13.3です。


## プロジェクトの作成

Xcode→File→NewProjectとし、最上段をmacOS(iOSではない)とし、APPを指定します。次のページに移って、プロダクト名は原作と同じTestCoreData、言語はObjective-Cにします。UseCoreDataのチェックをお忘れなく。
チーム名などはNoneのままでOKです。

<h2>Entityの設定</h2>

原作と全く同じで、新しいEntityを作り、名前をMonsterとします。そのAttributeは一つだけで、NameはmonsterName、TypeはStringとします。<br>
EntityのMonsterを選択し、右側のCodegenというところがClass Definitionになっているのを確認して下さい。多分デフォールトでそうなっている筈です。

<h2>ViewController.m</h2>
ちょっと乱暴ですが、元のコードをすべて削り、代わりに次のコードをこのままペーストして下さい。これをやらないと、次の作業の間ずっとXcodeが文句をいってきます。


```objc
//
//  ViewController.m
//  TestCoreData
//
//  Created by Myself on 2022/03/16.
//

#import "ViewController.h"

  ViewController *viewctrl_ptr;		/* (referenced by 'AppDelegate') */





@implementation ViewController

{
 
#pragma mark global variables within ViewController

  AppDelegate *parent_ptr;

  NSArray<NSManagedObject *> *monstary;		/* (handled by Core Data) */

  NSManagedObjectModel *my_mom;
  NSPersistentStoreCoordinator *my_psc;
  NSManagedObjectContext *my_moc;
  NSFetchRequest *my_fetch;
}





#pragma mark initializers

- (void)viewDidLoad

{ [super viewDidLoad];
  viewctrl_ptr = self;		/* set public variable */

  tbl_view_ptr.delegate = self;
  tbl_view_ptr.dataSource = self;

  monstary = nil;		/* (against some accident) */
} // 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





#pragma mark event handler

- (IBAction)editdone:(NSTextField *)sender

{ NSString *mystring;

  NSLog(@"notification from text-field");
  [txtfield_ptr resignFirstResponder];

  mystring = txtfield_ptr.stringValue;
  if ( mystring == nil )
    NSLog(@"textfield not yet ready");
  else if ( mystring.length == 0 )
    NSLog(@"textfield empty");
  else
  { [self createOneWithName: mystring];	/* create one Monster */
    [tbl_view_ptr reloadData];
    txtfield_ptr.stringValue = @"";	/* clear textfield */
  }
} // editdone





#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");

  my_fetch = [[NSFetchRequest alloc] initWithEntityName: @"Monster"];

  errorptr = nil;	/* (unnecessary ... result not used) */
  temp_ary = [my_moc executeFetchRequest: my_fetch error: &errorptr];
  if ( temp_ary == nil )
    NSLog(@"monster array not yet ready");
  else			/* (usually array ready) */
    monstary = temp_ary;
} // initcore





- (void)createOneWithName: (NSString *)the_name

	/* create and save one entity */

{ NSManagedObject *one_monster;
  NSEntityDescription *myentity;
  NSError *errorptr;
  BOOL myresult;

	/* create one Monster(entity) */

  NSLog(@"start creating Monster");
  myentity = [NSEntityDescription entityForName:@"Monster"
	      inManagedObjectContext: my_moc];
  one_monster = [[NSManagedObject alloc]
    		 initWithEntity: myentity
    		 insertIntoManagedObjectContext: my_moc];
  NSAssert(one_monster != nil, @"managed object creation failed");

	/* set attribute (Monster name) */

  [one_monster setValue: the_name forKey: @"monsterName"];

	/* save the Monster */
	/* update monster-array */

  NSLog(@"start saving monster");
  errorptr = nil;	/* (&errorptr = '\0') */
  myresult = [one_monster.managedObjectContext save: &errorptr];
  if ( ( myresult == NO ) || ( errorptr != nil ) )
    NSLog(@"monster '%@' save error", the_name);
  else			/* (here errorptr == nil) */
  { NSLog(@"new monster with name '%@' saved", the_name);
    monstary = [my_moc executeFetchRequest: my_fetch error: &errorptr];
    NSAssert(monstary != nil, @"monster-array fetch failed");
  }
} // createOne





#pragma mark delegate and datasource

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView

{ NSInteger nbr_rows;		/* number of rows */

  if ( monstary == nil )	/* monster array not ready */
    nbr_rows = 0;
  else
    nbr_rows = monstary.count;

  return nbr_rows;
} // numberOfRows





- (id)tableView:(NSTableView *)aTableView
	objectValueForTableColumn:(NSTableColumn *)aTableColumn
	row:(NSInteger)rowIndex

{ NSString *mystring;
  NSManagedObject *one_monster;

  if ( monstary == nil )	/* array not ready (never occurs?) */
    mystring = @"";
  else
  { one_monster = [monstary objectAtIndex: rowIndex];
    mystring = [one_monster valueForKey: @"monsterName"];
  }

  return mystring;
} // objectValue

@end

```

Xcodeはまだ何か文句をいっていますが、先に進みましょう。

<h2>ViewController.h</h2>

変更は少しだけです。

```objc
#import <CoreData/CoreData.h>	// ← これを追加する

#import "AppDelegate.h"		// ← これを追加する

@interface ViewController : NSViewController
	<NSTableViewDelegate, NSTableViewDataSource> // ← これを追加する

	/* -- public functions (called by 'AppDelegate') -- */

	// AppDelegateから呼ばれるfunctionを2行追加
	
- (void)setParent_ptr:(AppDelegate *)myparent;
- (void)set_pc;
```

<h2>AppDelegate.m</h2>

変更は次のとおりです。

```objc
#import "AppDelegate.h"
#import "ViewController.h"		// ← これを追加する

  extern ViewController *viewctrl_ptr;	// ← これを追加する

@interface AppDelegate ()


- (IBAction)saveAction:(id)sender;

@end

@implementation AppDelegate

	// この関数の内容を次のようにする
	// この関数はViewDidLoadよりも後で呼ばれる
	// だからviewctrl_ptrはすでにセットされている筈

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification

{ NSAssert(viewctrl_ptr != nil, @"ViewController not ready");
  [viewctrl_ptr setParent_ptr: self];
  [viewctrl_ptr set_pc];
	// Insert code here to initialize your application
}
```

AppDelegate.hの変更はありません。<br>
(投稿直前になって気が付いたのですが、viewctrl_ptrの初期値をnilにしておくべきでした。このままではNSAssertの意味がありませんよね:sweat_smile: 。)<br>


<h2>ストーリーボードの設定</h2>


Viewに部品TextFieldをセット(ドラッグ・ドロップ)します。<br>
TextFieldのActionがSent On End Editingになっているのを確認します。<br>
TextFieldの下に部品TableViewをセットします。<br>
TableViewのContent ModeをCell Based、列数を1にします。<br>
TableViewにはTextCellと表示されていますが、ここにTextFieldCellをセットします。似たような部品が多いのでご注意を。<br>
<br>
ここから3本、コネクションをやります。アシスタントエディタを開き、ViewController.mを表示させます。<br>
TextFieldからコントロール・ドラッグして、ViewController.mの@implementationのすぐ下のブレースの中にIBOutletを作り、名前をtxtfield_ptrにします。<br>
TableViewからコントロール・ドラッグして、さっきの下にIBOutletを作り、名前をtbl_view_ptrにします。<br>
最後にTextFieldからコントロール・ドラッグして、ViewController.mの中程にある(IBAction)editdoneにコネクトします。<br>
なおdelegateとdatasourceはViewController.mで手当てしているので、処理は不要です。<br>

<h2>試運転</h2>

ビルドさせて実行し、テキストフィールドにモンスター名を入力してエンターキーを押すと、テーブルに名前が表示されます。デバッグウィンドウで作業内容がわかりますね。<br>
試運転が終わったらストップボタンを押して下さい。<br>


<h2>アプリケーションを作成</h2>

Xcode→Product→Archiveに進むと、アーカイブが作成されます。作成後の表示を見失ったらXcode→Window→Organizerで同じ画面が表示されます。<br>
Distribute Appからcopy Appとし、お好きなフォルダーを選んで下さい。Finderツールを使って見ると堂々たる(?)自作アプリケーションの完成です。拡張子もちゃんとappになっていますね。<br>
Xcodeを終了し、Finderツールでアプリケーションをダブルクリックすると、Xcodeなしでアプリが起動し、メニューバーにはアプリ名が表示されます。<br>
モンスター名をいくつか追加し、メニューバーからアプリを終了させ、再び起動させると、追加分も正しく表示されているのがわかります。

<h2>終わりに</h2>
最初に申し上げたとおり、コードが自己流ですみません。変数名や関数名にいわゆるキャメルをほとんど使っていませんが、お見逃し下さい。
viewctrl_ptrのようなバグがほかにもありそうです。いろいろご指摘をお待ちしております。