頭と尻尾はくれてやる!

パソコンおやじのiPhoneアプリ・サイト作成・運営日記


KVOによる値の変化を監視する

Cocoa MVC
Cocoa Core Competencies: Model-View-Controllerより

MVCデザインパターンを使うとMからCへ通知する、という仕組みを作るものらしい。delegateでいいんじゃねえのって思うんだけど、Mが複数のCやMに参照されうるのでdelegateではなく通知なんだそうな。なるほど、delegateだとそこへしかメッセージ送れないからね。

ということでKVO(Key-Value Observing)という仕組みを使って値の変化を監視、通知するというのを試してみたんだ。そう、今まで通知なんて使ったことなかったのよ。

調べてみたところこの通知には自動と手動の二種類あるんみたいなんだ。


それじゃあまず、自動の方ね。
ControllerクラスがModelクラス内の値を監視してて、その値が変化したらControllerへ通知が行くようにするわけだ。
まずControllerクラス内に記述するのがこんなの。
{
    [model addObserver:self forKeyPath:@"counter" options:NSKeyValueObservingOptionNew context:nil];
}
forKeyPath:ってところで文字列を指定してるけど、これなんと監視するプロパティを示すのよ。なんかダイレクトだなあって驚いたんだけどプロパティ名の文字列そのものなのよ。
optionsで指定した内容によってやってくる通知でいろんな値が拾える。上のなんちゃらNewというのを指定すると変化後のプロパティの値を後で拾うことができたりする。

同じControl内にその通知をもらう部分はこんな感じで記述↓
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqual:@"counter"]) {
        int newValue = [[change valueForKey:@"new"] intValue];
        NSLog(@"newValue=%d",newValue);
        //viewへの処理など
    }
}
これはこういうメソッドがNSObjectにあってユーザーがテキトウに考えるメソッド名じゃないよ。ここでkeyPathって文字列を調べて該当するなら処理する、って流れ。監視の設定時にオプションで新しい値をもらう、なんてのを指定するとchangeってNSDictionaryオブジェクトでもらえる。

次に監視される方の設定。Modelクラス内の記述ね。自動か手動かを下のようなメソッドで判断する。
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey
{
    BOOL automatic = NO;
        if ([theKey isEqualToString:@"counter"]) {
        automatic = YES;
    } else {
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}
あとは実際に値が変更する部分が当然必要でModelクラス内にこういうメソッドがあってControllerからコールされるんだろうね。
-(void)countUp
{
    self.counter++;
}
この時、_counter++;だと意図通り動かなくて上のようにsetterを通さないとだめだったよ。つまりreadonlyのプロパティだと自動の通知ができないってことになる。


readonlyのプロパティやModelクラス内のインスタンス変数の値の変化を監視するんだ!って場合には自動じゃなくて手動にしないとだめっぽい。
手動の場合は
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
メソッドで該当するキーだとNOを返すようにする。自動の時と反対だね。
それと値変更の前後で次のような記述をするのよ。
{
    [self willChangeValueForKey:@"counter"];
    counter++;
    [self didChangeValueForKey:@"counter"];
}
ここでcounterはインスタンス変数。コードだけ見てると
willChangeValueForKey:
didChangeValueForKey:
の後にある文字列を指定してる部分はただのキーだから監視する部分で指定するキーの文字列と同じにしとけばなんでもいいのかなと思っていたら、インスタンス変数と同じにしないと動かないんだね。ちょっとはまったよ。


参考サイト Cocoaサンプル - MVC - white wheelsのメモ


UIViewオブジェクトのみIBを使ってみる

MVCデザインパターンというのを意識するとふとV(ビュー)はコード書かなくていいんじゃない?IBでいいんじゃないの?なんて思ったので試してみたよ。ずーっとInterface Builder(もちろんStoryboardも)は避けてきたんだけどね。

ViewのxibをロードしてカスタムViewとひもづける – Cyber Passion for iOS
↑参考にしたのがこちらのサイト。
これでCの部分はコード、VはIBで記述して、VとCを紐付けることができたんだけど、結局Vは
View.xib
View.h
View.m
という三つのファイルが必要になることに。xibファイルだけで完結したら最強なんだけどなあ、と思うものの例えばView内にボタンを置いたらそれをタップした時の処理を記述するのに結局はコードを書かないとだめになるから〜.hと〜.mが必要になるのは仕方ないか。

もっともボタンを置かずにUILabelオブジェクトや画像を表示するだけとかなら〜.mの方はなくても大丈夫だけど、それでも中身の大してないヘッダーファイルは必要になる。まあそんなビューってあまりないと思うけど。

ここはあきらめて、Vの部分はxib,h,mの三つのファイルで構成し、xibに見た目に関わる部分を極力記述するってことにするかな。


Blenderでレンダリング結果が真っ黒な理由

blender.org - Home of the Blender project - Free and Open 3D Creation Software
↑最近Blenderの練習してるのよ。そのうち3Dプリンタも安くなって普段から使うようになるかもしれないから、こういうソフトに慣れておくのもいいかな、とか。それに無料だしね。

Blender入門(2.6版)
↑お世話になってる親切丁寧なチュートリアルサイト。まずはこのサイトを見ながらてんとう虫を作ってみたよ。
俺のがMacなのとキーボードにテンキーがないのでサイト通りにはいかないのに結構悩んだりするけどまあなんとかなるかな。

Macだからというわけじゃないけど一番はまったのが、どういうわけかレンダリングすると物体が全て真っ黒になっちゃうんだ。
もちろん、ライトもセットしてるし、カメラもある。テクスチャも設定してるつもりなんだけど?
しかも昨日まではちゃんといけてたのに、、、なんてこともあってこれはBlenderの不具合なのかな?なんて思考停止状態へ陥るところだったけど、いろいろ調べたところ真っ黒になる原因がわかったんだ。

上のチュートリアルサイトに従ってカメラとライトは別のレイヤに移動していたんだよ。

Blenderレイヤーオフ
↑下の方でレイヤのオンオフ制御できるところあるよね。カメラとライトはレイヤ11に置いてて、これをオフにしてると作業時にカメラとライトが見えないので作業しやすいんだよね。

真っ黒になったレンダリング結果
↑この状態でレンダリングすると真っ黒になっちゃうんだよ。

Blenderレイヤーオン
↑次にカメラとライトレイヤをオンにする。画面下にあるレイヤ選択部分でレイヤ11もオンになってるでしょ。

タマゲダケのレンダリング結果
↑レンダリングすると意図通り表示されるんだよ。
そういうことかあ、って感じだよね。単に表示の問題でレンダリング結果に反映されると思わなかったよ。


ちなみに作っていたのはタマゲダケというポケモン。イッシュ地方のポケモンで一番造形が簡単そうなのを練習台にしてみたんだ。まあモデリングの練習とうことで細かいツッコミは勘弁してね。


構造体を入れた配列をNSDataに変換したい

構造体を入れた配列(NSArray)をNSDataに変換しようとしたらはまっちゃったよ。

↓まずはこういう構造体があるとするよ。
typedef struct {
    int age;
    NSString *name;
} Person_t;
そもそも構造体だとNS(Mutable)Arrayには入らないからNSValueに変換してNS(Mutable)Arrayに入れていたのよ。 配列とのやりとりはこんなカテゴリで。
@implementation NSMutableArray (AddPersonInfo)
-(void)addPersonInfo:(Person_t)personInfo
{
    NSValue *value = [NSValue value:&personInfo withObjCType:@encode(Person_t)];
    [self addObject:value];
}
@end

@implementation NSArray (GetPersonInfo)
-(Person_t)personInfoAtIndex:(int)index
{
    NSValue *value = [self objectAtIndex:index];
    Person_t personInfo;
    [value getValue:&personInfo];
    return personInfo;
}
@end
この方法で配列NS(Mutable)ArrayにどんどんaddしてできたNSMutableArrayオブジェクトをNSData化したくなったんだよ。CoreDataで持たせる、MultipeerConnectivityで送信する、、、みたいにNSData化したい時ってあるよね。
理由はともかくこのNSMutableArrayオブジェクトをNSData化しようと次のようなコードを書いたんだよ。
{
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
}
ホントはこれもカテゴリ化してるんだけどそれはどうでもいいや。
ともかくこれを実行すると落ちるのよ。
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSKeyedArchiver encodeValueOfObjCType:at:]: this archiver cannot encode structs'

え?構造体はだめなの?NSValueにしてるからいいのかと思ったんだけど。
ところがね、配列に入れる時にNSValueじゃなくてNSDataにしてみたんだよ。こういう感じのメソッドを用意しといてさ。
#pragma mark 変換処理(構造体←→NSData)
-(NSData *)dataFromPersonInfo:(Person_t)personInfo
{
    //NSDataに変換する
    return [NSData dataWithBytes: &personInfo length: sizeof(Person_t)];
}

-(Person_t)personInfoFromData:(NSData *)data
{
    //NSDataからPerson_tを得る
    Person_t personInfo;
    [data getBytes:&personInfo length:sizeof(Person_t)];
    return personInfo;
}
すると、その配列はすんなりとNSData化できたのよ!どういうこと?

構造体→NSValue→配列に入れる→NSData化 失敗
構造体→NSData→配列に入れる→NSData化 成功

配列にどんな形で入れるか?というだけなんだけどねえ。
ちなみにNSValueもNSDataもNSCodingプロトコルに準拠してるからどちらでも成功しそうなものなんだけど、、、そうならないのはきっと俺が何か理解していないんだろうな。

ともかくNSDataにしたいのなら構造体ではなくNSCodingプロトコルに準拠したクラスでやっとけって話だよね。


SyntaxHighlighterでXcode風に表示

SyntaxHighlighter
↑ブログでコードを記述する時に使ってるのがSyntaxHighlighter。このブログでもずっと使ってるんだけど、Objective-CはなかったのでC#で指定してたのよ。まあいいかって感じでさ。
何を思ったかふと調べてみたらこんなサイトがあったんだ。
アプリ開発の記録 SyntaxHighlighterでObjective-Cをきれいに表示
Objective-Cに対応しているのはもちろん、面白いことにXcode風の配色に修正したとか。それじゃあってことでお借りして使ってみることにしたよ。しばらくこれでやってみる。

#pragma mark デバッグ用
@implementation UIAlertView (ShowMessage)
+(void)showMessage:(NSString *)text
{
    [[[[UIAlertView alloc] initWithTitle:nil message:text delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show];
}
@end
↑Objective-CでXcode風だとこんな感じ。いいんじゃない?




Copyright ©頭と尻尾はくれてやる!. Powered by FC2 Blog. Template by eriraha.