74

私はブロックをたくさん使い始めましたが、すぐにnilブロックがバスエラーを引き起こすことに気づきました。

typedef void (^SimpleBlock)(void);
SimpleBlock aBlock = nil;
aBlock(); // bus error

これは、nilオブジェクトへのメッセージを無視するObjective-Cの通常の動作に反しているようです。

NSArray *foo = nil;
NSLog(@"%i", [foo count]); // runs fine

したがって、ブロックを使用する前に、通常のnilチェックに頼る必要があります。

if (aBlock != nil)
    aBlock();

または、ダミーブロックを使用します。

aBlock = ^{};
aBlock(); // runs fine

別のオプションはありますか?nilブロックが単なるnopになれない理由はありますか?

4

4 に答える 4

148

これについてもう少し詳しく説明し、より完全な答えを示したいと思います。まず、このコードについて考えてみましょう。

#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {    
    void (^block)() = nil;
    block();
}

これを実行すると、次のblock()ような行でクラッシュが発生します(32ビットアーキテクチャで実行した場合、これは重要です)。

EXC_BAD_ACCESS(コード= 2、アドレス= 0xc)

それで、それはなぜですか?さて、これ0xcが最も重要なビットです。クラッシュは、プロセッサがメモリアドレスの情報を読み取ろうとしたことを意味します0xc。これはほぼ間違いなく完全に間違ったことです。そこに何かがある可能性は低いです。しかし、なぜこのメモリ位置を読み取ろうとしたのでしょうか。まあ、それはブロックが実際にボンネットの下に構築される方法によるものです。

ブロックが定義されると、コンパイラーは実際にスタック上に次の形式の構造体を作成します。

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

この場合、ブロックはこの構造体へのポインターになります。この構造の4番目のメンバーであるinvoke、は興味深いものです。これは関数ポインタであり、ブロックの実装が保持されているコードを指します。したがって、ブロックが呼び出されると、プロセッサはそのコードにジャンプしようとします。メンバーの前の構造体のバイト数を数えるとinvoke、10進数で12、または16進数でCであることがわかります。

したがって、ブロックが呼び出されると、プロセッサはブロックのアドレスを取得し、12を加算して、そのメモリアドレスに保持されている値をロードしようとします。次に、そのアドレスにジャンプしようとします。ただし、ブロックがnilの場合は、アドレスを読み取ろうとします0xc。これは明らかにダフアドレスであるため、セグメンテーション違反が発生します。

さて、Objective-Cメッセージ呼び出しのように黙って失敗するのではなく、このようなクラッシュでなければならない理由は、実際には設計上の選択です。コンパイラはブロックの呼び出し方法を決定する作業を行っているため、ブロックが呼び出されるすべての場所にnilチェックコードを挿入する必要があります。これにより、コードサイズが大きくなり、パフォーマンスが低下します。別のオプションは、nilチェックを行うトランポリンを使用することです。ただし、これにはパフォーマンスの低下も発生します。Objective-Cメッセージは、実際に呼び出されるメソッドを検索する必要があるため、すでにトランポリンを通過しています。ランタイムはメソッドのレイジーインジェクションとメソッド実装の変更を可能にするので、とにかくすでにトランポリンを通過しています。この場合、nilチェックを実行することによる追加のペナルティは重要ではありません。

それが理論的根拠を説明するのに少し役立つことを願っています。

詳細については、私のブログ 投稿を参照してください。

于 2013-02-05T09:10:44.713 に答える
40

マットギャロウェイの答えは完璧です!よく読んでください!

人生を楽にする方法がいくつかあることを付け加えたいと思います。次のようなマクロを定義できます。

#define BLOCK_SAFE_RUN(block, ...) block ? block(__VA_ARGS__) : nil

0〜n個の引数を取ることができます。使用例

typedef void (^SimpleBlock)(void);
SimpleBlock simpleNilBlock = nil;
SimpleBlock simpleLogBlock = ^{ NSLog(@"working"); };
BLOCK_SAFE_RUN(simpleNilBlock);
BLOCK_SAFE_RUN(simpleLogBlock);

typedef void (^BlockWithArguments)(BOOL arg1, NSString *arg2);
BlockWithArguments argumentsNilBlock = nil;
BlockWithArguments argumentsLogBlock = ^(BOOL arg1, NSString *arg2) { NSLog(@"%@", arg2); };
BLOCK_SAFE_RUN(argumentsNilBlock, YES, @"ok");
BLOCK_SAFE_RUN(argumentsLogBlock, YES, @"ok");

ブロックの戻り値取得したいが、ブロックが存在するかどうかわからない場合は、次のように入力する方がよいでしょう。

block ? block() : nil;

このようにして、フォールバック値を簡単に定義できます。私の例では「nil」です。

于 2012-10-23T18:52:02.023 に答える
9

警告:私はブロックの専門家ではありません。

ブロック Objective-cオブジェクトですが、ブロックや他のメッセージを試すことはできますが、ブロックの呼び出しはメッセージではありません。[block retain]nil

うまくいけば、それ(およびリンク)が役立つことを願っています。

于 2010-11-10T14:18:46.573 に答える
2

これは私の最も単純な解決策です…多分それらのcvar-argsで1つのユニバーサル実行関数を書くことは可能ですが、それを書く方法がわかりません。

void run(void (^block)()) {
    if (block)block();
}

void runWith(void (^block)(id), id value) {
    if (block)block(value);
}
于 2012-05-07T07:54:02.580 に答える