2011年9月28日水曜日

Objective-Cのオブジェクト初期化エラーに対処する方法

当たり前だけどオブジェクトの初期化に失敗することもあるわけで、その場合はリソースの解放などエラー処理が必要になる。以前書いたクラスを見直していたら、イニシャライザのエラー処理で怪しいコードがあったので正しい方法を調べてみた。 

AppleのThe Objective-C Programming Languageを見てみると答えがそのまま書かれていて、selfに対してreleaseを呼んでからnilを返すとなっている。releaseによってdeallocが呼ばれるので、self自体とインスタンス変数に割り当てたオブジェクトが解放されることになる。

画像データをNSDataで受け取りUIImageとして保持するクラスがあったとすると、次のような感じになる。

- (id)initWithData:(NSData*)data
{
    self = [super init];
    // superのイニシャライザがnilを返してもreleaseは必要ない
    // 既にsuperで行われているため
    if (self) {
        image = [[UIImage alloc] initWithData:data];
        if (!image) {
            // imageの解放は必要ないけどself自体の解放が必要
            [self release];
            return nil;
        }
    }
    return self;
}

- (void)dealloc
{
    // 初期化に失敗していてもimageはnilなので問題ない
    [image release];
    [super dealloc];
}

deallocは初期化に失敗した状態で呼ばれることもあるため、そのような場合でも正しく動くようにしなければいけない。とは言え、上記のコードのように単純にインスタンス変数をreleaseするだけなら問題はない。

  1. allocはメモリを割り当てた後、すべてのインスタンス変数を0にセットする(0 == nil
  2. インスタンス変数に割り当てるオブジェクトの初期化に失敗しても、そのオブジェクトのイニシャライザはnilを返すため、インスタンス変数はnilにセットされる
  3. nilに対するメソッドの呼び出しは何も起きないことが保証されている

したがって、インスタンス変数はオブジェクトが割り当てられているか、nilがセットされているかのどちらかなので、deallocは正常に実行される。

ちなみに、free(NULL)delete 0も何も起きないことが保証されている。ただし、CFReleaseはリファレンスによると、

If cf is NULL, this will cause a runtime error and your application will crash.

とのことなのでNULLチェックが必要。

2011年8月17日水曜日

HomebrewでCocoa Emacs 23.3aをインストールした

(追記)HomebrewでCocoa Emacs 24.2をインストールした。

少し前にMacBook Pro 13インチ (Early 2011)を購入して、いろいろと設定するついでに、これまで使ってきたCarbon EmacsからCocoa Emacs (Emacs 23)へ移行することにした。最初はバイナリで簡単にインストールできると思っていたけど、日本語入力が完全ではないようでパッチを当てたほうがいいことがわかった。パッケージ管理はHomebrewで行っているのだけど、Formulaを書き換えることでパッチの追加も簡単にできるし、EmacsもHomebrewでインストールすることにした。

パッチについて

パッチはいろいろ公開されているけど、日本語入力を快適にするためのインラインパッチ(IMEパッチ)は必須のようだ。また、インラインパッチのバグを修正するパッチがCocoa Emacs が落ちるで公開されているので使わせて頂く。

さらに、feature-fullscreen.patchも定番らしい。これはM-x ns-toggle-fullscreenでEmacsがフルスクリーンになる機能を追加する。同じリポジトリでfix-shiftmodifier-with-ime.patchも公開されていて、こちらは日本語IMEでShiftが使えない問題を修正する。これはインラインパッチが提供する機能で回避した方がいいらしいので使わないことにする。

Homebrewでインストール

HomebrewでFormulaを編集するには

$ brew edit emacs

のようにする。Formulaを開いた状態で$EDITORが立ち上がるので書き換えて保存すればいい。

実際にFormulaを開いて気付いたのだけど、すでにfullscreen patchは含まれている(バージョンは23.3a)。ということで、必要なのはインラインパッチとそのバグを修正するパッチ、あと宣言と定義で引数の型が違ってコンパイルエラーになってしまう部分があったので、それを修正するパッチも追加した(Xcodeのバージョンは3.2.6)。

最終的にpatchesメソッドは次のようになった。

(2011/11/12 追記)最新のFormulaに合わせて修正(バージョンは23.3aのまま)。Fix for Shift key ...はインラインパッチとかぶるのでコメントアウトした。

def patches
  p1 = []
  p0 = []

  # Fix for building with Xcode 4; harmless on Xcode 3.x.
  unless ARGV.build_head?
    p1 << "http://repo.or.cz/w/emacs.git/commitdiff_plain/c8bba48c5889c4773c62a10f7c3d4383881f11$
    # Fix for address randomization on Darwin. Based on:
    #   http://repo.or.cz/w/emacs.git/patch/f2cea124dffac9ca4b8ce1dbb9b746f8e81109a3
    p1 << "https://raw.github.com/gist/1098107"
    # Fix for the titlebar issue on Mac OS X 10.7
    p1 << "https://raw.github.com/gist/1102744"
    # Fix for Shift key for IME users
    #p1 << "https://raw.github.com/gist/1212776"
  end

  if ARGV.include? "--cocoa"
    # Fullscreen patch, works against 23.3 and HEAD.
    p1 << "https://raw.github.com/gist/1012927"

    p0 << "http://sourceforge.jp/frs/redir.php?m=jaist&f=%2Fmacemacsjp%2F47986%2Finline_patch-23.2-beta3.tar.gz"
    p0 << DATA
  end

  return { :p1 => p1, :p0 => p0 }
end

Hashを返すと:p1の値はpatch -p1に、:p0の値はpatch -p0に渡される。

さらに、ファイルの一番最後に以下を追加する。

__END__
diff -U0 src/macim.m.orig src/macim.m
--- src/macim.m.orig
+++ src/macim.m
@@ -42 +42 @@
-int mac_pass_key_to_system (int code, UInt32 modifiers);
+int mac_pass_key_to_system (int code, unsigned modifiers);
diff -U0 src/nsterm.m.orig src/nsterm.m
--- src/nsterm.m.orig
+++ src/nsterm.m
@@ -3959 +3959 @@
-  if (mac_store_change_input_method_event())
+  if (emacs_event && mac_store_change_input_method_event())

Rubyでは、__END__以降はスクリプトとしては認識されず、DATAという定数でファイルオブジェクトとして扱うことができる。

Formulaの編集が終わったら次のようにしてインストールする。Emacs.appは、/usr/local/bin/emacsなどのシンボリックリンクが切れてしまうけど、自分はターミナルで使用することはないのでmvした。

$ brew install emacs --cocoa
$ mv /usr/local/Cellar/emacs/23.3a/Emacs.app /Applications

初期設定

最低限必要な設定は以下の通りで、2行目が日本語IMEのShiftの問題を回避する。

(setq default-input-method "MacOSX")
(mac-add-key-passed-to-system 'shift)

デフォルトではOptionキーがMetaになっている。CommandキーをMetaとして使うためにOptionキーと入れ替える。

(setq ns-command-modifier (quote meta))
(setq ns-alternate-modifier (quote super))

ドラッグ&ドロップでファイルを開く。2行目は新しいウィンドウで開くのを防ぐため(Cocoa Emacs で dnd - Think Different - はてな版)。

(define-key global-map [ns-drag-file] 'ns-find-file)
(setq ns-pop-up-frames nil)

円記号の代わりにバックスラッシュを入力する。ただし、delete-horizontal-spaceなどのキーバインディングで、M-¥ is undefinedなどと表示されてしまう。

(mac-translate-from-yen-to-backslash)

フォント設定はこちらのページが参考になる。

2011年7月26日火曜日

Black DiamondのSpotとエネループ

岩場の帰りやナイトボルダリングなど、ヘッドランプはボルダラーにも必須ということでBlack Diamondのスポットを買った。事前にネットで調べてみると、Amazonのレビューにエネループ使用だと蓋がしっかり閉まらないとの情報があった。すでに持っているエネループで使用するつもりだったけど、通電はしているということなので購入に踏み切った。

購入後、実際に試してみるとわずかに隙間ができてしまうだけで、蓋も「カチッ」と音を立てて閉まったし点灯も普通にした。昔のエネループは現行のよりもサイズが大きかったらしいので、それだと閉まらないのかもしれない。

隙間で心配なのが防水性。

スポットは防水規格IPX4の防水性能があり、いかなる方向からの水の飛沫にも影響されません。

とのことだけど、隙間が空いてたらこの性能は期待できなさそう。まあ、そもそもパッキンも使ってないし、どっちにしろバッテリーハウジングの中には水が入るような気がしないでもない。一応気をつけることにする。

ところで、エネループに限らずニッケル水素電池で使う場合は過放電に気をつける必要がある。三洋電機のよくあるご質問 (FAQ) eneloop 単1形~単4形から引用。

自動的に放電を停止する機能が無い機器で使用すると、過放電(電池を使いすぎてしまう)となる場合があります。例えば、消費電力が小さいLEDライトで点灯しなくなるまで使用した場合、過放電となり、その後に充電した時に、うまく充電ができなくなる可能性があります。

尚、過放電をくり返すと電池にダメージを与えますので、LEDライトで光量が大きく低下した等、電池のパワーが無くなった感じた時は、使用を止めて、充電することをお勧めします。

というわけで、バッテリーメーターが赤になったらなるべく早く交換なり充電なりしたほうがよさそう。メモリー効果は回復できるけど、過放電は取り返しがつかないので。と言っても、エネループはメモリー効果の影響をほとんど受けないみたいだけど。

ニカド電池やニッケル水素電池を使い切らすに継ぎ足し充電をくり返すと「短時間だけ使用」を記憶して、次に使用した時、電圧がすぐに下がり機器が停止する場合があります。これをメモリー効果といいます。eneloopは従来のニッケル水素電池よりも電圧がもともと高く、メモリー効果が起こっても十分な電圧を維持するため、その影響はほとんどなく、メモリー効果を気にせす、継ぎ足し充電することも可能です。

2011年5月27日金曜日

コンパクトサイズの竹ブラシ発見

豚毛の竹ブラシが安くてチョークもよく落ちるというようなことを少し前に書いたけど、サイズが大きくて使いづらくもある。チョークバッグのブラシホルダーには入らないし、細かいホールドだと先端の角ばかり使って斜めに減ってきてしまう(天然毛は摩耗して短くなる)。

ということで、小さな竹ブラシはないものかと思っていたら、この前とは別のホームセンターで発見した。「竹楊子」と書かれていて値段は48円だった。一般的な歯ブラシと比べると、柄のサイズは同じぐらいだけどヘッドは大きめ。ブラシホルダーは問題なく使える。残念ながら毛の密度が低いため、1ストロークで取り除けるチョークの量は少ない。

(追記)さらに今度は「重慶ブラシ」という竹ブラシを同じホームセンターで発見。どうやら前回行ったときは売り切れていたようだ。これは竹楊子よりも一回り大きいけどブラシホルダーにはなんとか刺さるし(200mmと240mmの2サイズがあるけど200mmのほう)、ヘッドのサイズも特に使いにくさを感じることはない。また、毛の密度がだいぶ大きいため少ない回数でクリーニングできる。ということで、追記になってしまったけどこちらがおすすめ。

2011年4月30日土曜日

クライミングで使うブラシについて

小山田大 DVDでボルダリングの付属DVDの中でブラシはとても重要なものと小山田さんが言っていた。ということで、少なからずパフォーマンスにも関わってくるブラシについて書いてみる。

自分でも実際に試してみたのだけど、毛の材質はナイロンより豚毛や馬毛などの天然毛のほうがよくチョークが落ちる。普通の歯ブラシはナイロンが使われていて、毛の先端が細くなっているなど工夫が凝らされていたりするけど、天然毛と比べるとあまり落ちない。メトリウスのM16ボルダリングブラシもナイロン。

天然毛では、豚毛より馬毛のほうがブラッシングしたときに舞うチョークの量が多いような気がする。豚毛は硬く、馬毛は柔らかいのでひょっとしたら使い分けるのがいいかもしれない。

豚毛のブラシは、ホームセンターなどで黒い毛の竹ブラシを探せば簡単に手に入る。値段も100円前後で安い。天然毛は使うにつれ毛が減っていく消耗品なのだけど、この値段だったらジムで使っても気にならない。デメリットは柄が大きくてブラシホルダーに入らないこと。小さいのもあるらしいが自分が行ったホームセンターには置いてなかった。

(追記)コンパクトサイズの竹ブラシを発見した。

馬毛は柄のついている適度な大きさのものはあまり売っていないようだ。ブラシ専門店ではもちろん扱っているけど、もっと身近なところで手に入らないかと探してみたら、「軟毛ライオン」という歯ブラシを見つけることができた。毛は馬とヤギの混合とのことだけど、使ってみるとよくチョークが舞ったので、馬毛ブラシの代替としても十分使えるというのが自分の感想。

近所のドラッグストアを覗いてみたら、3件中3件とも置いてあったので入手もしやすい。自分はたしか298円で購入したが、ブラシ専門店のものより少し安いし今後はこれを使っていこうと思う。

2011年3月31日木曜日

インサイドフラッギングの使い過ぎに注意

もはやインサイドフラッギングは自分にとって特別なムーブではない。使えるところでは何も考えずに足が動くようになった。

インサイドフラッギング再考では次のムーブで踏み替えが必要になったとしても、合計の足数は変わらないのでインサイドフラッギングができる体勢ならとりあえずやっとけというようなことを書いた。実際に踏み替えを極力避け、それを実践してきたのだけど、ごく稀にインサイドフラッギングの後でなにやらバランスの悪いとても苦しい体勢になってしまうことがあった。

思い起こしてみると、次のホールドを取って内側に流している足を抜くところまではいいのだけど、その後正体になってしまい、その正体のバランスが非常に悪いというような感じ。正体になるのは構わないのだけど、バランスが悪いのは困る。

ということで、インサイドフラッギングの後で正体になりそうな場合はバランスも考慮してムーブを決める。体を返せそうな場合は迷わずインサイドフラッギング。という方針でファイナルアンサー。

2011年2月28日月曜日

NSArray/NSMutableArrayから重複要素を削除する

NSArrayNSMutableArrayには重複する要素を取り除くメソッドが用意されていない。要素の順序がどうなってもいいなら、NSSetが重複を許さないことを利用して

[[NSSet setWithArray:array] allObjects];

とすることもできるけど、そうでない場合も多々ある。また、セレクタやBlocksで等値判定をしたいこともある。

(追記)iOS 5からはNSOrderedSetが使えるので

[[NSOrderedSet orderedSetWithArray:array] array];

で、順序を保ったまま重複要素の削除ができる。セレクタやBlocksで等値判定を行う場合は下記参照。

NSMutableArray

ということでカテゴリで実装することにした。とりあえずNSMutableArrayから。

- (void)removeDuplicateObjects
{
    NSMutableIndexSet* removedIndexes = [[NSMutableIndexSet alloc] init];
    NSMutableSet* set = [[NSMutableSet alloc] init];
    const NSUInteger count = [self count];
    for (NSUInteger i = 0; i < count; i++) {
        id object = [self objectAtIndex:i];
        if ([set containsObject:object]) {
            [removedIndexes addIndex:i];
        } else {
            [set addObject:object];
        }
    }
    [set release];
    [self removeObjectsAtIndexes:removedIndexes];
    [removedIndexes release];
}

等値判定はもちろん–isEqual:で行われるのだけど、高速化のためNSSetを利用しているので、–hashも適切に定義されている必要がある(最初に挙げたNSSetNSOrderedSetを使った例も–isEqual:–hashが必要)。また、重複要素のうち前にあるほうを残すため、逆向きにループしながら削除する方法は使えずNSMutableIndexSetを使用した。

次に等値判定をセレクタで行う場合。こちらは後ろからループして、各要素について重複要素がないか一つずつ調べていき、見つかればただちに削除している。

- (void)removeDuplicateObjectsUsingSelector:(SEL)selector
{
    for (NSUInteger i = [self count]; i > 0; i--) {
        id object = [self objectAtIndex:i - 1];
        BOOL (*imp)(id, SEL, id) = (BOOL(*)(id, SEL, id))[object methodForSelector:selector];
        for (NSUInteger j = 0; j < i - 1; j++) {
            if (imp(object, selector, [self objectAtIndex:j])) {
                [self removeObjectAtIndex:i - 1];
                break;
            }
        }
    }
}

セレクタの返り値がBOOLなのでIMPを取り出して関数呼び出しを行っているけど、それについてはperformSelectorで返り値がid型以外のメソッドを呼ぶを参照。if文の条件式にperformSelectorの返り値をそのまま使ったら常に真になってしまう場合があって、バグの発見に時間がかかってしまった。

Blocksを使うとこんな感じになる。

- (void)removeDuplicateObjectsUsingBlock:(BOOL (^)(id a, id b))block
{
    for (NSUInteger i = [self count]; i > 0; i--) {
        id object = [self objectAtIndex:i - 1];
        for (NSUInteger j = 0; j < i - 1; j++) {
            if (block(object, [self objectAtIndex:j])) {
                [self removeObjectAtIndex:i - 1];
                break;
            }
        }
    }
}

NSArray

NSArrayの場合はカテゴリで

- (NSArray*)arrayByRemovingDuplicateObjects
{
    NSMutableArray* mutableCopy = [self mutableCopy];
    [mutableCopy removeDuplicateObjects];
    NSArray* uniqueElements = [NSArray arrayWithArray:mutableCopy];
    [mutableCopy release];
    return uniqueElements;
}

のように定義すればいいと思う。自分は必要なかったため実装していないけど、セレクタやBlocksを使う場合も同じようにして簡単に書ける。

テスト

Xcode 3.2.5 and iOS SDK 4.2で確認した。

@interface NSString (Private)
- (BOOL)isCaseInsensitiveEqualToString:(NSString*)other;
@end

@implementation NSString (Private)
- (BOOL)isCaseInsensitiveEqualToString:(NSString*)other
{
    return [self compare:other options:NSCaseInsensitiveSearch] == NSOrderedSame;
}
@end

@interface NSMutableArrayUniqueTest : SenTestCase
@end

@implementation NSMutableArrayUniqueTest

- (void)testRemoveDuplicateObjects
{
    NSMutableArray* array;
    NSMutableArray* expected;
    
    array = [NSMutableArray array];
    [array removeDuplicateObjects];
    expected = [NSMutableArray array];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObject:@"a"];
    [array removeDuplicateObjects];
    expected = [NSMutableArray arrayWithObject:@"a"];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObjects:@"a", @"b", nil];
    [array removeDuplicateObjects];
    expected = [NSMutableArray arrayWithObjects:@"a", @"b", nil];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObjects:@"a", @"a", nil];
    [array removeDuplicateObjects];
    expected = [NSMutableArray arrayWithObject:@"a"];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObjects:@"a", @"b", @"a", @"b", nil];
    [array removeDuplicateObjects];
    expected = [NSMutableArray arrayWithObjects:@"a", @"b", nil];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObjects:@"a", @"b", @"a", @"a", @"b", @"c", nil];
    [array removeDuplicateObjects];
    expected = [NSMutableArray arrayWithObjects:@"a", @"b", @"c", nil];
    STAssertEqualObjects(expected, array, @"");
}

- (void)testRemoveDuplicateObjectsUsingBlock
{
    NSMutableArray* array;
    NSMutableArray* expected;
    BOOL (^block)(id, id) = ^BOOL(id a, id b) {
        return [a compare:b options:NSCaseInsensitiveSearch] == NSOrderedSame;
    };
    
    array = [NSMutableArray array];
    [array removeDuplicateObjectsUsingBlock:block];
    expected = [NSMutableArray array];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObject:@"a"];
    [array removeDuplicateObjectsUsingBlock:block];
    expected = [NSMutableArray arrayWithObject:@"a"];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObjects:@"a", @"b", nil];
    [array removeDuplicateObjectsUsingBlock:block];
    expected = [NSMutableArray arrayWithObjects:@"a", @"b", nil];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObjects:@"a", @"A", nil];
    [array removeDuplicateObjectsUsingBlock:block];
    expected = [NSMutableArray arrayWithObjects:@"a", nil];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObjects:@"a", @"B", @"A", @"b", nil];
    [array removeDuplicateObjectsUsingBlock:block];
    expected = [NSMutableArray arrayWithObjects:@"a", @"B", nil];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObjects:@"a", @"B", @"A", @"a", @"b", @"c", nil];
    [array removeDuplicateObjectsUsingBlock:block];
    expected = [NSMutableArray arrayWithObjects:@"a", @"B", @"c", nil];
    STAssertEqualObjects(expected, array, @"");
}

- (void)testRemoveDuplicateObjectsUsingSelector
{
    NSMutableArray* array;
    NSMutableArray* expected;
    
    array = [NSMutableArray array];
    [array removeDuplicateObjectsUsingSelector:@selector(isCaseInsensitiveEqualToString:)];
    expected = [NSMutableArray array];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObject:@"a"];
    [array removeDuplicateObjectsUsingSelector:@selector(isCaseInsensitiveEqualToString:)];
    expected = [NSMutableArray arrayWithObject:@"a"];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObjects:@"a", @"b", nil];
    [array removeDuplicateObjectsUsingSelector:@selector(isCaseInsensitiveEqualToString:)];
    expected = [NSMutableArray arrayWithObjects:@"a", @"b", nil];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObjects:@"a", @"A", nil];
    [array removeDuplicateObjectsUsingSelector:@selector(isCaseInsensitiveEqualToString:)];
    expected = [NSMutableArray arrayWithObjects:@"a", nil];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObjects:@"a", @"B", @"A", @"b", nil];
    [array removeDuplicateObjectsUsingSelector:@selector(isCaseInsensitiveEqualToString:)];
    expected = [NSMutableArray arrayWithObjects:@"a", @"B", nil];
    STAssertEqualObjects(expected, array, @"");
    
    array = [NSMutableArray arrayWithObjects:@"a", @"B", @"A", @"a", @"b", @"c", nil];
    [array removeDuplicateObjectsUsingSelector:@selector(isCaseInsensitiveEqualToString:)];
    expected = [NSMutableArray arrayWithObjects:@"a", @"B", @"c", nil];
    STAssertEqualObjects(expected, array, @"");
}
@end

2011年1月31日月曜日

performSelectorで返り値がid型以外のメソッドを呼ぶ

返り値がid型以外のメソッドをperformSelectorで呼ばなければならないことがあるけど、performSelectorの返り値の型はidになっているため、floatや構造体などはキャストしてもコンパイルが通らない。また、コンパイルできたとしても返ってきた値をそのまま使うと問題になる場合もある。

performSelectorをリファレンスで調べると

For methods that return anything other than an object, use NSInvocation.

と書いてあるけど、NSInvocationを使うのはとても面倒だし、パフォーマンスも落ちるのでできるだけ使いたくない。

何かいい方法はないものかと調べてみると、comp.lang.objective-CのFAQにCan I use SEL for methods returning non-id types?という項目と、そのすぐ上にCan I use IMP for methods returning non-idtypes?という項目が見つかった。回答を短くまとめると、IMPを取り出してその関数ポインタを適切にキャストしてから呼び出せばいい、ということなのでやってみた。

NSNumber* number = [NSNumber numberWithFloat:M_PI];
NSLog(@"%f", [number floatValue]); // => 3.141593

float (*floatValueImp)(id, SEL) = (float(*)(id, SEL))[number methodForSelector:@selector(floatValue)];
NSLog(@"%f", floatValueImp(number, @selector(floatValue))); // => 3.141593

返り値が構造体でも問題ない。

NSValue* value = [NSValue valueWithRange:NSMakeRange(11, 13)];
NSLog(@"%@", NSStringFromRange([value rangeValue])); // => {11, 13}

NSRange (*rangeValueImp)(id, SEL) = (NSRange(*)(id, SEL))[value methodForSelector:@selector(rangeValue)];
NSLog(@"%@", NSStringFromRange(rangeValueImp(value, @selector(rangeValue)))); // => {11, 13}

ということで、関数ポインタのキャストが見にくいけど、NSInvocationを使うよりはいいんじゃないかと思った。

IMPを使うことですべて解決したわけだけど、そもそものきっかけは、

if ([a performSelector:@selector(methodThatReturnsBOOL:) withObject:b]) {
    ...
}

というように、BOOL値を返すメソッドをperformSelectorで呼んで、そのままif文の条件式に使っていたところ、ブロックがまったく実行されないという現象が起きたことだった。

NSLog(@"%d", [a performSelector:@selector(methodThatReturnsBOOL:) withObject:b])

のようにして値を表示してみると、セレクタが0を返していても-256と表示された。型のサイズが変わっているのが原因だと思うのだけど(idは構造体へのポインタ、BOOLsigned chartypedefされている)、特定の状況でしか起きなかったため発見するのに時間がかかってしまった。なお、BOOLの場合は返り値をキャストすることでも解決する。