SEO対策:Objective-C SEO対策:Cocoa

    NSBundleでファイルパスが取れない場合

    プロジェクトに任意のファイルを登録して、NSBundle を使ってパスを取得し、読み込むということは良くある処理。
    しかしながら、時々 NSBundle でパスが取れない場合があった。

    下記のようなコードを書いてパスを取得しようと思っても・・・

    NSString *path = [[NSBundle mainBundle] pathForResource:@"hoge.so" ofType:nil];

    プロジェクトに hoge.so があるのに、path は nil になっている。

    これは実際にビルドされて出来上がったファイル群に hoge.so が入っていないからである。
    Xcodeに不要と思われて外されてしまった。

    では、どうやって解決するか。
    Xcodeに手動で「hoge.so も是非仲間に入れてくれ」とお願いする。

    登録方法は下記の画面にある「Copy Bundle Resources」に hoge.so を追加する。
    「+」ボタンを押せば追加したいファイルを選択できる。

    Copy Bundle Resources


    この設定を行った上でビルドすれば NSBundle でパスが取得できる。

    UIWebViewでフック

    UIWebView を使ってリクエスト送信時に処理を奪ったり、レスポンスのHTMLを取得したりする方法。
    UIWebView のデリゲート(UIWebViewDelegate)、以下に示すメソッドを実装する。

    UIWebView で表示した画面でリンクを押したり、ボタンを押したりしたときに次のメソッドが呼ばれる。

    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
    {
    NSString *url = [[[request URL] standardizedURL] absoluteString];
    NSLog(@"url=%@", url);

    NSString *body = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
    NSLog(@"body=%@", body);

    return YES;
    }

    アクセスしたURLやリクエストボディを得ることができる。
    戻り値は YES にするとリクエストを完了させる。NO だと処理を中断してしまう。
    URL を見ていろいろ処理を変えるというような使い方が可能。

    ちょっとセキュリティ的にどうなの?と思うのは、https のページであってもリクエストボディから平文でフォームに入力した値が取れてしまうということだ。
    OAuth による認証なんかでも送信リクエスト値が奪えてしまう。。。
    悪いことしてはいけません。

    navigationType パラメータには何がされたかを示す定数が入っている。

    enum型で下記が定義されている。
     UIWebViewNavigationTypeLinkClicked
     UIWebViewNavigationTypeFormSubmitted
     UIWebViewNavigationTypeBackForward
     UIWebViewNavigationTypeReload
     UIWebViewNavigationTypeFormResubmitted
     UIWebViewNavigationTypeOther

    名前でだいたい想像つくと思う。
    詳しくはリファレンス参照。


    次にレスポンスのHTMLを取得。

    - (void)webViewDidFinishLoad:(UIWebView *)webView
    {
    NSString *body = [webView stringByEvaluatingJavaScriptFromString:@"document.body.innerHTML"];

    NSLog(@"innerHTML=%@", body);
    }

    Javascript を使って <body> の innerHTML を取得できる。

    というか、Javascript が使えるので何でもできる。

    NSString *body = [webView stringByEvaluatingJavaScriptFromString:@"document.evaluate('//body/div', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.innerHTML"];

    のように Xpath を使うことだってできる。

    NSNotificationCenter

    NSNotificationCenter を使った通知。
    これもよく登場する。

    ①通知してほしい処理の登録

    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

    [nc addObserver:foo selector:@selector(invoke:) name:@"call1" object:nil];
    [nc addObserver:foo selector:@selector(invoke:) name:@"call2" object:nil];
    [nc addObserver:bar selector:@selector(invoke:) name:@"call1" object:nil];


    それぞれ上から、
     call1 という名前で通知されたら、fooインスタンスの invoke メソッドを呼んでもらう。
     call2 という名前で通知されたら、fooインスタンスの invoke メソッドを呼んでもらう。
     call1 という名前で通知されたら、barインスタンスの invoke メソッドを呼んでもらう。
    という意味になる。
    ちなみ -(void)addObserver: selector: name: object してもインスタンスは保持されない。

    ②通知する

    NSNotification *n = [NSNotification notificationWithName:@"call1" object:nil];

    [[NSNotificationCenter defaultCenter] postNotification:n];


    call1 という名前で通知して、①で登録した [foo invoke] と [bar invoke] を実行する。

    ③通知を削除する

    [[NSNotificationCenter defaultCenter] removeObserver:kanshi name:@"call1" object:nil];

    [nc addObserver:foo selector:@selector(invoke:) name:@"call1" object:nil]; の分だけが削除される。



    [[NSNotificationCenter defaultCenter] removeObserver:kanshi name:nil object:nil];

    name に nil を渡すと、
    [nc addObserver:foo selector:@selector(invoke:) name:@"call1" object:nil];
    [nc addObserver:foo selector:@selector(invoke:) name:@"call2" object:nil];
    した分が削除される。

    キー値監視

    Cocoaフレームワークでしばしば登場するキー値監視。Key Value Observing や KVO とも。
    「あるインスタンスのプロパティが更新されたときに、何かしらの処理をしたい」
    そういうシーンで利用する。
    つまり、Observerパターン。

    まずはモデルを定義。
    nameプロパティを持つ単純なクラス。

    @interface Model : NSObject
    @property (nonatomic, assign) NSString *name;
    @end

    @implementation Model
    @synthesize name;
    @end


    続いて、プロパティが変更されたときに行いたい処理を書く。


    @interface Kanshi : NSObject
    @end

    @implementation Kanshi

    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if([keyPath isEqualToString:@"name"]) {
    NSLog(@"%@", [object valueForKey:@"name"]); // nameプロパティの値が取れる
    }

    }
    @end



    -(void)observeValueForKeyPath: ofObject: change: context:
    という長い名前のメソッドをオーバーライドしているのがコツ。

    この2つのクラスを使ってテストしてみる。


    int main()
    {
    @autoreleasepool {
    Model *model = [[Model alloc] init];
    Kanshi *kanshi = [[Kanshi alloc] init];

    [model addObserver:kanshi forKeyPath:@"name" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:callback];

    [model setValue:@"hoge" forKey:@"name"];
    model.name = @"hoge2";

    [model removeObserver:kanshi forKeyPath:@"name"];

    [model setValue:@"hoge3" forKey:@"name"];

    [kanshi release];
    [model release];
    }

    return 0;
    }


    まず最初に -(void)addObserver: forKeyPath options: context: を使って監視対象のインスタンスに、更新があった際に通知してほしいインスタンスを登録する。
    いくつでも登録することができる。
    forKeyPath には監視したいプロパティ名を、options は後述する。
    context は(void *)なので何でも好きなポインタを渡せば良い。

    続いて、敢えて二通りの方法でプロパティに値をセットしている。
    キー値コーディングと普通のセッターで値をセットしている。
    どちらの方法でも通知されるということを示したかっただけ。
    つまりこのコードでは、2回プロパティが更新されたことになるので、当然通知は2回される。

    さて、通知されると書いたがどういうことか?
    先ほど、-(void)observeValueForKeyPath: ofObject: change: context: という長いメソッドをオーバーライドした。
    いよいよ出番だ。

    監視対象のプロパティが更新されるたびに上記の長いメソッドが呼ばれる。
    keyPath にはプロパティ名が、object には監視対象となっているインスタンスが、change には変更の状態が、context には任意のポインタが入っている。
    change は -(void)addObserver: forKeyPath options: context: のoptionsに渡した値によって登録されている内容が異なる。


    change の中身。
    {
    kind = 1; <- 常に入っている。意味はよくわからん。
    new = hoge; <- NSKeyValueObservingOptionNewを指定したときに入っている(今の値が)
    old = "<null>"; <- NSKeyValueObservingOptionOldを指定したときに入っている(1つ前の値が)
    }


    特に用途がなければ options:0 で良いでしょう。

    context は addObserver時の context に渡したポインタが入っている。
    関数ポインタを渡してコールバック関数を呼び出しても良いし、せっかくの Objective-C なのでセレクタ(SEL型)を渡しても良いし、他のクラスのインスタンスを渡しても良い。
    ご自由に。

    さて、では後始末について。
    監視対象のインスタンスの -(void)removeObserver: forKeyPath: を呼んで削除する。
    第一引数には通知先として登録したインスタンス、第二引数には監視対象としていたプロパティ名を。

    ちなみに -(void)addObserver: forKeyPath options: context: で通知先として登録したインスタンスは保持されない。
    [kanshi retainCount] の値が変わらないことはすぐに観察できる。

    最後にもう一回、name プロパティを更新しているが通知は行われない。

    C言語で動的確保したメモリサイズを取得(デバッグ用)

    iOS開発でややこしいのは、しばしばObjective-Cのメモリ管理とC言語のメモリ管理が混在してしまうことだ。
    iOS5ではARCという新しいメモリ管理方式が出現してObjective-Cでのメモリ管理がラクになった。
    しかし、C言語で書いた場合はARCは意味をなさない。
    なのでC言語で書かれたライブラリなんかを使う場合には要注意だ。

    前置きはここまでにして、mallocしてメモリを動的確保した場合、実際に確保したサイズを取得したいと思ったらどうしたら良いか。
    良く間違いとして登場するのが以下のコードだ。


    char *p_c = (char *)malloc(100);

    NSLog(@"%ld", sizeof(n)); // char * のサイズ
    NSLog(@"%ld", sizeof(*n)); // char のサイズ


    いずれも100byteが返るわけではない。

    実際に確保したメモリのサイズを得るのはこうだ。


    #import <malloc/malloc.h> // <-これをインポートしておく

    char *p_c = (char *)malloc(100);
    NSLog(@"%ld", malloc_size(p_c));


    これでおそらく112が返ってくる。
    詳しくは「man 3 malloc_size」コマンドでリファレンスを参照してほしい。
    大きいサイズで返ってくる場合があると記載されている。

    たぶん16byteのブロック単位で確保するからだと思う。
    アラインメントってやつか。


    char *p_c1 = (char *)malloc(97);
    NSLog(@"%ld", malloc_size(p_c1)); // -> 112byteになる

    char *p_c2 = (char *)malloc(96);
    NSLog(@"%ld", malloc_size(p_c2)); // -> 96byteになる

    char *p_c3 = (char *)malloc(1);
    NSLog(@"%ld", malloc_size(p_c3)); // -> 16byteになる



    ただしこれ、Linuxではmalloc_usable_size関数が同じような役割を持っていて、そちらでは大きい値が返ってきたからといってmallocした以上のサイズに書き込むのはダメなよう。
    malloc_sizeのmanでは特にそこまで書かれていないが、あくまでもデバッグ用の参考値程度に使おう。
    プロフィール

    Author:O++
    iOS修行中。
    @ksgejp

    最新記事
    最新コメント
    最新トラックバック
    月別アーカイブ
    カテゴリ
    検索フォーム
    Amazon(1)
    Amazon(2)
    RSSリンクの表示
    リンク
    Powered By FC2ブログ

    今すぐブログを作ろう!

    Powered By FC2ブログ

    ブロとも申請フォーム

    この人とブロともになる