LoginSignup
8
7

More than 5 years have passed since last update.

NSInvocation で getReturnValue をするときの注意点

Last updated at Posted at 2013-12-31

はじめに

このエントリは第14回yidevでもお話させていただいた内容になります。
スライドはこちらだけど、多分大事なことは口頭で話てしまったのであんまり参考にならないかも(汗

長々と書いてもアレなので、まずは結論から。
NSInvocation の getReturnValue を使うときは自分で Retain Count をマネジメントすること!!

サンプルコード

Githubにサンプルコードを置きました
この記事はサンプルコードをベースに説明が書かれているので、こちらのコードをダウンロードしてください。

NSInvocationについて

基本的な使い方やパラメータの渡し方についてはサンプルやこちらの記事を参考にどうぞ。
NSInvocation を使ってクラスメソッドを呼ぶ

なんでNSInvocation?

今回私がNSInvocationを使うことになったのは、リーダブルコードを読んで影響をうけて、コードの冗長性はなるべく排除していいコードを書きたい!と思うようになったから。
とはいえ、そもそもNSInvocationが必要になるシーン自体もそこまで多くはないのでその前に設計について考えてみることも大事です。

NSInvocation を使う前

話がそれましたが、NSInvocation の getReturnValue について。
まずはサンプルの SimpleChapterViewController.m, SimpleDAO.m をご覧ください。
Chapter <-> Section <-> Paragraph という構造を持つデータベースからデータを引っ張ってきてテーブルに描画するサンプルとなっています。

だた、SimpleDAO.m を見て分かる通りかなり記述が冗長なのと、どんなデータを取りたいというSQLの部分が目立たない感じの記述になっています。

NSInvocation を使った後

InvokedDAO.m を見てください、だいぶスッキリしました。
ただ、InvokedChapterViewController.m を表示させようとすると落ちてしまいます。
どこかと思って Instruments で探ってみると、NSInvocationの返り値で取得したNSDictionaryがReleaseされているのが原因でした。

InvokedDAO.m
            // 注:サンプルではここのフラグを false に変えると正常に動作するほうに分岐されます
            if (true) {
                NSDictionary *dataDictionary;

                // invocate method by NSInvocation.
                SEL selector = NSSelectorFromString([NSString stringWithFormat:@"%@:", factoryMethod]);
                NSMethodSignature* signature = [[self class] methodSignatureForSelector:selector];
                NSInvocation* invocation = [ NSInvocation invocationWithMethodSignature:signature];
                [invocation setSelector:selector];
                [invocation setTarget:[self class]];
                [invocation setArgument:&statement atIndex:2];
                [invocation invoke];
                [invocation getReturnValue:&dataDictionary];

                [result addObject:[NSDictionary dictionaryWithDictionary:dataDictionary]];
            }

Instrumentsの画像

よくよくAppleの公式ドキュメントを見ると、たしかに Retain とかしねーよ?って書いてあります。
(今回の件は arguments じゃなくて returnValue のほうなのだけど、どうやらそれも同じっぽい)

This class does not retain the arguments for the contained invocation by default. If those objects might disappear between the time you create your instance of NSInvocation and the time you use it, you should explicitly retain the objects yourself or invoke the retainArguments method to have the invocation object retain them itself.

retainArguments について言及されていますが、今回は返り値のみが retain されればいいので、CFRetainを使うように記述したところうまくいきました。
参考文献:StackOverFlow

InvokedDAO.m
                CFTypeRef data;

                // invocate method by NSInvocation.
                SEL selector = NSSelectorFromString([NSString stringWithFormat:@"%@:", factoryMethod]);
                NSMethodSignature* signature = [[self class] methodSignatureForSelector:selector];
                NSInvocation* invocation = [ NSInvocation invocationWithMethodSignature:signature];
                [invocation setSelector:selector];
                [invocation setTarget:[self class]];
                [invocation setArgument:&statement atIndex:2];
                [invocation invoke];
                [invocation getReturnValue:&data];

                // When out of this method scope, then released data. So, use CFRetain and go well.
                // http://stackoverflow.com/questions/7078109/why-does-nsinvocation-getreturnvalue-loose-object/11569236#11569236
                if (data) CFRetain(data);
                NSDictionary *dataDictionary = (__bridge_transfer NSDictionary *)data;

                [result addObject:[NSDictionary dictionaryWithDictionary:dataDictionary]];

Instruments を実行してみても、ちゃんと画面遷移に応じてインスタンスが確保されて画面が消えるとメモリ解放されているのがわかります。

メモリ確保・開放の様子

おわりに

Appleの公式ドキュメントはよく読もう!w
そして リーダブルコード!!
q.jpg

8
7
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7