2013-11-22 12 views
12

ここでは、私が尋ねようとしているのは、教育的、おそらくデバッグ目的のみであることを述べることで、この質問の前に説明します。NSBlockオブジェクトはどのように作成されますか?

Objective Cランタイムでブロックオブジェクトは内部的にどのように作成されますか?

さまざまなブロックタイプをすべて表すクラスの階層が表示され、NSObject以下の階層内の最高のスーパークラスはNSBlockです。クラスデータのダンプは、+ alloc,+ allocWithZone:+ copyおよび+ copyWithZone:のメソッドを実装していることを示しています。他のどのブロックサブクラスもこれらのクラスメソッドを実装していないので、おそらく誤って、NSBlockがブロック処理を担当すると私は信じています。

しかし、これらのメソッドは、ブロックの生存時間のどの時点でも呼び出されていないようです。私は自分自身で実装を交換し、それぞれにブレークポイントを設定しましたが、決して呼び出されることはありません。 NSObjectの実装と同様の練習をすることで、私が欲しいものを正確に得られます。

ブロックが異なる方法で実装されていると仮定しますか?誰でもこの実装の仕組みを明らかにすることができますか?私がブロックの割り当てとコピーを行うことができなくても、私は内部実装を理解したいと思います。

+0

私は[ 'を使用することが可能であるとは思いませんNSBlock alloc] 'ブロックを作成し、呼び出されない理由を示します。 –

答えて

11

tl; dr

コンパイラはブロックリテラルを構造体と関数に直接変換します。そのため、allocコールが表示されません。


議論

ブロックは本格的なObjective-Cのオブジェクトですが、この事実はめったに彼らは非常に面白い獣作り、その使用に露出していません。

最初の奇抜な点の1つは、ブロックが一般的にスタック上に作成されることです(グローバルブロック、すなわち周囲のコンテキストを参照しないブロックでない限り)。今日では、それらはスタック上に確保できる唯一のObjective-Cオブジェクトです。

おそらく、割り当てのこの奇妙さのために、言語設計者は、ブロックリテラル(つまり、^演算子を使用)を介したブロック作成を許可することにしました。 このように、コンパイラはブロック割り当てを完全に制御しています。

としてはclang specificationで説明し、自動的に2つの構造体と、各ブロックのための少なくとも1つの機能を生成するコンパイラはリテラルは遭遇:

  • ブロックリテラル構造体
  • ブロック記述子構造体
  • リテラル

    ブロック呼び出したとえば機能

コンパイラは以下

struct __block_literal_1 { 
    void *isa; 
    int flags; 
    int reserved; 
    void (*invoke)(struct __block_literal_1 *); 
    struct __block_descriptor_1 *descriptor; 
}; 

void __block_invoke_1(struct __block_literal_1 *_block) { 
    printf("hello world\n"); 
} 

static struct __block_descriptor_1 { 
    unsigned long int reserved; 
    unsigned long int Block_size; 
} __block_descriptor_1 = { 0, sizeof(struct __block_literal_1), __block_invoke_1 }; 

(方法によって、そのブロックがグローバルブロックとして適格で、それがメモリに固定された場所に作成される)

生成する32ビットシステムで
^ { printf("hello world\n"); } 

ブロックはObjective-Cオブジェクトですが、低レベルの方法であり、ポインタはisaポインタの構造体です。正式な観点からは、NSBlockの具体的なサブクラスのインスタンスですが、Objective-C APIは割り当てに使用されることはありませんので、allocコールは表示されません。リテラルはコンパイラによって構造体に直接変換されます。

+0

グローバルブロックがヒープ上に作成されたとは思いません。リンクしたドキュメントは、ブロックがスタックブロックになることを明確に示しています(抜粋したコードの直後)。 (それは意味があります;ランタイムデータがないとヒープに置くのはなぜですか?)また、私はスタックに割り当てられている唯一の目的のオブジェクトではないと思います。 64ビットABIでは、オブジェクト全体がポインタに埋め込まれる可能性があります。そのため、レジスタ割り付けだけでも可能です。 :) –

+1

@JesseRusakグローバルブロックは、 'NSString'リテラルによく似ています。彼らは一定のオブジェクトであるため、固定された場所に住んでいます。それらをスタック上に作成するには、スタックフレームがビルドされるたびにそれらを割り当てる必要がありますが、周囲のコンテキストへの参照を保持しないため、最適化されています。これはここでよく説明されています:http://www.cocoawithlove.com/2009/10/how-blocks-are-implemented-and.html –

+0

@ JesseRusak、64ビットの懸念についてそれが実際に行われたことを示す言及はありません。私の知る限り、すべてのObjective-Cオブジェクトはブロックの例外を除いて、ヒープ上にのみ存在します。もちろん、あなたが提供できる余分なリファレンスを持っていれば私は間違っていることを証明したいです:) –

2

ブロックは基本的にコンパイラの魔法です。通常のオブジェクトとは異なり、実際にはスタックに直接割り当てられます。コピーするとヒープに配置されます。

Clangのblock implementation specificationを読んで、舞台裏で何が起こっているかを知ることができます。私の理解では、短いバージョンは、ブロックタイプとその捕捉された状態を表すstructタイプとブロックを呼び出す関数が定義されており、ブロックへの参照は構造タイプの値に置き換えられますその呼び出しポインタは、生成された関数に設定され、そのフィールドは適切な状態で埋められます。

6

他の回答で説明したように、ブロックオブジェクトはグローバルストレージ(コンパイラ)またはスタック(コンパイル済みコード)で直接作成されます。それらは最初はヒープ上に作成されません。

ブロックオブジェクトは、ブリッジされたCoreFoundationオブジェクトに似ています。Objective-Cインターフェイスは、基礎となるCインターフェイスのカバーです。ブロックオブジェクトの-copyWithZone:メソッドは_Block_copy()関数を呼び出しますが、一部のコードでは_Block_copy()を直接呼び出します。つまり、-copyWithZone:のブレークポイントはすべてのコピーをキャッチしません。

(はい、あなたはプレーンなCコードでブロックオブジェクトを使用することができます。qsort_b()機能とatexit_b()機能がありますと、ええと、それはそれであるかもしれない。)

+0

こんにちは、グローバルストレージはスタックに等しくないのですか? – Unheilig

関連する問題