Class method と Instance method について
アプリを作っていて、画面ごとに(というか ViewController ごとに)source が分かれるのですが、あ、この method 前の画面でも使ったなぁということがあります。今回遭遇したのは、field に数字を入力したら自動的にカンマをつける、データを保存するときは、カンマを取ってから保存するというような method が必要になりました。どちらの ViewController にも同じ method を定義してももちろんよいのですが、MyUtilities のようなClass を新設し、そこに書いたほうが将来の保守性はよい訳です。
というわけで、Class を新設して method を定義していったのですが、そういえば、Instance method と Class method があったなと。定義する時、頭が - なら Instance method で、+ なら Class method になります。この違いですが、O'reilly の本などを読んでも今ひとつピンとこないのですが、Class method の方が code を書く時に便利です。Instance method では次のようになります。
MyUtilities* hoge = [[MyUtilities alloc] init];
[hoge someMethod:variables];
これを Class method として定義してある場合は、インスタンスを生成する必要がないので、一行で書けてしまうと。
[MyUtilities someMethod:variables];
それで、Class method は一体何の役に立つのだ?ということなんですが、refactoring の時が一番メリットを感じます。例えば、同じ class 内に method を定義していて、この method を別の class に定義したとすると、code は次のように変更することになります。
変更前:[self someMethod:variables];
変更後:[SomeClass someMethod:variables];
つまり、self の部分だけ変えればよいのです。これが、instance method だった場合は、かなり面倒なことになります。
SomeClass* hoge = [[SomeClass alloc] init];
[hoge someMethod:variables];
一行では到底書けません。
実は、Class method の方が、定義する時にめんどくさいこともあったりするのですが、クラスのインスタンスを生成しなくてもよいのでコードを減らすことができる、あるいは refactoring の際に簡単に直せるというのが一番の利点なのかなと思いました。
UIProgressView にハマる・・・
あるフィールドに値を入れたら、UIProgressView の表示も更新したいと思い、
・アニメーションなしなら、progressView.progress = xx;
・アニメーションありなら、[progressView setProgress:xx animated:YES];
アニメーションの有無を指定できるところが、iOS っぽくていいよね、楽勝・楽勝!と思っていたら思いっきりハマった・・・
理由は、フィールドの値をもとにして最新の値を使って progress の値を指定する必要があったのだが、計算に保存してある初期値を使っていたので結局のところ値を更新したつもりが更新されていなかった、progress の値が変更されていないので見た目の変化がないという結果に・・・
それに気づくまで結構時間がかかって、dispatch だの main_thread で実行しているからか?とか、画面に対して needsUpdate が必要か?などいろいろ試して全く変化がないので散々悩みましたが、理由がわかればなるほど、と。
あー、かなり疲れた・・・
トレイルランニング:愛宕山ー吾国山、縦走
今日は休みだったのでいつもの縦走コースでトレーニング。
朝五時半に起きて、岩間駅に着いたのが 0730 頃。最近寒いのでウエアが心配だったのですが、ジオライン+R1 の上にウインドブレーカーで、歩くと寒いけれども走り始めたらちょうど良い感じになりました。道中、下りの日陰ではやや寒いものの、運動を続けられるぐらいで、よかったです。
トレイルは冬なので周辺の草がなく、道中はいつもと違うように感じました。また、空気が澄んでいるので景観は素晴らしかったです。落ち葉ば多く、所々滑るので慎重に。このコース、登山道入り口から終始傾斜がきついと思うのですが、今回も傾斜がきついと感じました。登りも下りもあまりスピードが出せない。でも、決して歩かないと決めていたので、途中立ち止まって休んだものの走りきりました。今回は人にあったのは一人だけ。年末ですしあまり人が来ないのでしょうかね、この季節は景色が良いのでおすすめなんですが。
水戸線は確か 25 分発じゃないかと思い、一般道ではペースアップ。福原駅に着いたのが 1025、水戸線は 1019 発で、1055 まで待つ羽目に。風が吹いていなくて、太陽が出ているのでポカポカして暖かかったです。前回は確か、宍戸駅からはなさかまでヘロヘロになりながらだった気がしますが、今回は脚に随分余裕があって、楽々到着。そういえば水分も吾国山で 500ml 飲んだきり。気温が低いと疲れが少ないのか、あるいは普段のインターバル走の成果なのか。
はなさかで一風呂浴びて、さあ昼飯だと思ったら、食堂はいつもはあまり人がいないのですが今回は主に老人がたくさんいて、ステージを使ってカラオケ大会の様子。忘年会かなんかですかね。皆さん持ち歌があって、演歌中心に歌っていました。演歌じゃない曲が一曲だけあって、これなんだっけと思っていたらサビの部分で分かりました。イミテーション・ゴールド。うわこれなつかしいな、久々に聞きました。
お土産はいつもの吉原殿中を買ってきました。
また春に行ってこよう。
===翌日追記
朝起きたら、モモの前面が筋肉痛で辛い・・・
ヴェポライザーでシャグ:Domingo Menthol
C Vapor 2+ と HerbStick Relax を使っています。合うシャグがないかと思い色々試していますが、先日恵比寿に行った折に、昔ながらのタバコ屋がありシャグを物色。Domingo は吸ったことがないので買ってみました。
失敗しました・・・
どうやっても、ヴェポライザーでは楽しめません。加湿しないのが一番味わい深いように感じましたが、それでも軽い。キック感がない、全然ない。加湿するとマイルドになるけれども元々キック間のないところにマイルドになってどうすると。HerbStick Relax の底にティシュを詰め詰めキツキツにしてこれなら味出るだろと思ったけれども、改善なし。手強い。Choice に近いのかもしれない。あれも全然ダメだった。温度を上げると、お、いい感じかと思ったら、頭痛くなる味わい。もう、ダメだこれは。
しょうがないので、手巻きタバコとして使用することにしよう。はい、ローラーで巻き巻き。40g もあるので、消費するのが大変だ・・・ちなみに、Domingo とは、スペイン語で日曜日のこと。
今のところの、(オレオレ)シャグランキング(ヴェポライザー)
1. Colts Clear Menthol
2. Stanley Ice Mint
3. Che Menthol
×:domingo menthol
×:surfside
×:Choice
CoreData: 追加した object で、最近追加したものを上にするには・・・
CoreData に Orderd というチェックがあり、チェックしておくと detail の tableView で追加した順で並んで表示されるので便利。でも、detail の record は tableView で表示するのですが、record 数が多くなると、最近追加したものが上にある方が好ましいのは誰しも思うところ。
これを実現するにはどうするのかよく分からなかったんですが、ようやく分かりました。結論は、add ではなくて insert を使えということになります。こんな感じ。
変更前: [_detailItem addToEntityObject:newEntity];
変更後: [_detailItem insertObject:newEntity inToEntityAtIndex:0];
ポイントは、add... の場合、指定する必要のなかった index を指定する必要があります。一番上にしたいなら、常に index 0 を指定することになります。
ちなみに、in....AtIndex の ... 部分は relations で命名したものが使われます。ので、途中まで入力して tab キーを押して補完してもらうのがよいと思います。
って、これ、初めは表示するときにどうにかするんじゃないかと思って色々試行錯誤したんだけど上手くいかなくて、保存するときに method が用意されている、という顛末だったんで、調べるのに結構時間かかった・・・
CoreData: Master-Detail の detail 処理について
CoreData を使用するテンプレートで Master-Detail があり、とっかかりは重宝するのですが、立て込んだことをしようとすると色々わからないことが多い。
今日分かったのは、Detail の record 編集について。
Master から Detail の ViewController に移動しますが、そこはつまり Master の詳細画面な訳で、Detail に record を追加したり、あるいは編集したいという時のために Detail 用の ViewController が必要になります。言ってみれば Detail-Detail ということになるでしょうか。テンプレートの名称からして誤解が生まれそうなんですが、Master の 1 record を編集するための名称として Detail としている訳で、1 : N の N レコード一つ一つを編集する画面ではないということになります。
そんな構造が分かっているとしても、Detail に設置した tableView のレコードを追加する部分までは、Master-Detail のテンプレートからそれなりに応用が効くと思いますが、追加済みの N レコードを編集するにはどうすればよいか、はたと悩んでしまいました。こう言ったことに言及している記事がないので、オレオレですが書いておこうと思います。
まず、tableView に表示されているレコードが選択された時に呼ばれる delegate method はこれ:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
この method 内で、選択された cell をグローバル変数に入れておきます。次に、
performSegueWithIdentifier
で、強制的に prepareForSegue を呼びます。prepareForSegue では、遷移先の Class の property に値をセットしてやります。こんな感じですかね。
InputDetail* controller = (InputDetail *)[segue destinationViewController];
[controller setDetailDetail:selectedCell];
書いていませんでしたが、遷移先にはあらかじめ対応する property を設定しておく必要があります。上記の例では detailDetail という property になります(property を新設すると勝手に method が作られている、、というか、もともと Objective-C で property をそう定義してあります)。ところで、prepareForSegue で遷移するには理由があり、遷移先の ViewController (のポインタ)を取り出し易いのです。キャストしないと警告が出る点が嫌なんですが、他に書き方がわからないのと、キャストする都合、汎用的な書き方じゃないよね(他のプログラムにそのまま使い回せないね)というのが難点でしょうか。
遷移先では、property detailDetail に値があるかどうかで挙動を変えるようにします。書き方は、DetailViewController の template のが参考になります。
最後に、DetailDetail から Detail に戻ってきたときに呼ばれる method は次の通り。
- (IBAction) firstViewReturnActionForSegue:(UIStoryboardSegue *)segue
このメソッドでは、グローバル変数にセットした値をクリアしておく必要があります。なぜなら、record を追加しようとしているときも、選択したときも結局は prepareForSegue が呼ばれるので、追加したときもセルを選択した時のような挙動になるためです。
という訳で、できたのでよかったんですが、こういう解説があまりないのが CoreData の辛いところでしょうか・・・
CoreData Orded の処理
CoreData をいじっていたら、Orderd というチェックボックスがあることに気づいた。
名前からして、n 側の並び順を維持してくれるんじゃないかと期待が高まります。早速使ってみたら error 発生。なんで?
通常、n の record を取り出すには、array に突っ込みます。こんな感じで。
NSArray* relatedRecords = [_detailItem.toRelateMany allObjects];
でも、並び順を維持するには、array ではダメで、NSOrderdSet を使う必要があるのだ。
NSOrderedSet* relatedRecords = [[NSMutableOrderedSet alloc]initWithOrderedSet:_detailItem.toRelateMany];
それはよいのですが、class が違うので中身を取り出す時の method も、違う点を見逃してはならない。array の時は allObjects、orderedSet の時は initWithOrderedSet: となる。
ふぅぅ。