iOSベース:Blockの最下位実装と理解
16363 ワード
本論文では,最近blockの下位層を学習した後の理解を記録するために用いた.本文の参考博文:Block技巧と底層の解析はObjective-C blockの実現を話します
一、Blockコンパイル変換OC->C++
コマンド
二、Blockタイプ
1. NSConcreteGlobalBlock
以下の2つの場合、blockがNSConcreteGlobalBlock a.でグローバル変数を記述する箇所にblock構文がある場合.b.block構文の式で外部変数が使用されない場合.
更新(11.03:00):
ここには迅速な判断方法があります.Blockのbodyに外部の非グローバル変数と非static静的変数が使用されている場合、このBlockはスタック上に即_を作成します.NSConcreteStackBlock.逆に、参照変数がないか、グローバル変数のみが参照されているか、static静的変数がグローバルBlock_NSConcreteGlobalBlock由来:漫談Block
例1:
変換後のC++コード:
構造体
例2:main関数でblockを作成しようとしたが、blockは変数をキャプチャしなかったが、clang変換によりisaポインタがNSConcreteStackBlockを指していることが分かった.この点はおかしい.「ocプレミアムプログラミング」でこんな言葉を見ました.
関数内で広域変数が記述されていない場所でBlock構文を使用する場合でも、Blockが自動変数をキャプチャしない限り、Block用構造体インスタンスをプログラムのデータ領域に設定することができる.
2. NSConcreteStackBlock
スタックに保存されたblockは、関数が戻ると破棄されます.
変換後のC++コード:
構造体
3. NSConcreteMallocBlock
スタックに保存されたblockは、参照カウントが0の場合に破棄されます.しかしNSConcreteMallocBlockタイプのblockは通常ソースコードに直接現れず、
blockがcopyされると、
更新(11.03 9:45):
ありがとうございます.彼がくれたソースコードの中で以下のコードを見つけました.
構造体中のflagsによってcopyが判断されることは明らかである.
次にcopyの例を挙げると,ARCではBlockが関数として値を返すときにNSConcreteMallocBlockタイプにコピーされ,本質はcopyメソッドを呼び出すことである.次のコードがあります.
ARC環境では、システムが作成されたときに
3種類のBlockコピー:NSConcreteGlobalBlockコピー後、何もしません.NSConcreteStackBlockコピー後、スタックからスタックにコピーします.NSConcreteMallocBlockコピー後、参照カウントを1つ加算します.
四、_block変数のコピー
使用する場合ブロック修飾子の場合、基本データ型iは
ぐるぐる回っているのを見つけて、下に整理します.最も理解できないのは
『ocプレミアムプログラミング』で述べたように、
__ブロック修飾変数は構造体メンバー変数
五、Blockコピーペア_block変数の影響
に影響
Blockが使用していたら_ブロック変数、ブロックがスタックからスタックにコピーされると、a.スタックの_block変数はスタックにコピーされ、Blockによって保持されます.b.山の中の_block変数はBlockによって保持される.
これはOCの参照カウントメモリ管理と同じです.下にBlockA、BlockB、_block a、 __block b. BlockAがスタック上のaとbを使用している場合、[BlockA copy]がスタック上にコピーされると、a,bも同時にスタック上にコピーされ、スタック上のBlockAはスタック上のa,bを保持する.同様に、BlockA、BlockBはスタック上のaを使用し、[BlockA copy]、[BlockB copy]がスタック上にコピーされると、BlockAとBlockBはスタック上のaを同時に保持する.
4時の
上のコードをC++に変換すると、
いずれも
六、いくつかのテーマ、ARCとMRC環境の下で運行できるかどうかを判断する
1すべて実行可能
2 ARC運転可能
3すべて実行可能
4 ARC運転可能
5 ARC運転可能
七、更新(11.03 12:00)blockによる各種変数の処理
漫談Block後半を見て、その部分を整理したいと思いました.まずは!取得した変数が
1.変数が基本データ型無_block
blockの構造体内部にint a変数を追加するだけです.修正できません.
2.変数が基本データ型であり_block
あります_block修飾の基本データ型は
そして彼がコピーされると
コード発見により、
常に山の上の自分を指さすようにします.
3.変数がオブジェクトなし_block
デフォルトの
4.変数がオブジェクトにあります_block
直接付与.だからblock修飾は
一、Blockコンパイル変換OC->C++
コマンド
clang -rewrite-objc
を使用することによって実現される.1.まず、mainを新規作成します.mファイル.2.端末を開く、cdをmainに送る.mファイルが存在するディレクトリ.3.clang -rewrite-objc main.m
コマンドを入力して変換します.4.最後のmain.mファイルが存在するディレクトリの下でmainを新たに生成する.cppファイル.二、Blockタイプ
1. NSConcreteGlobalBlock
以下の2つの場合、blockがNSConcreteGlobalBlock a.でグローバル変数を記述する箇所にblock構文がある場合.b.block構文の式で外部変数が使用されない場合.
更新(11.03:00):
ここには迅速な判断方法があります.Blockのbodyに外部の非グローバル変数と非static静的変数が使用されている場合、このBlockはスタック上に即_を作成します.NSConcreteStackBlock.逆に、参照変数がないか、グローバル変数のみが参照されているか、static静的変数がグローバルBlock_NSConcreteGlobalBlock由来:漫談Block
例1:
#include
void (^globalBlock)() = ^{
};
int main()
{
globalBlock();
return 0;
}
変換後のC++コード:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __globalBlock_block_impl_0 {
struct __block_impl impl;
struct __globalBlock_block_desc_0* Desc;
__globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
構造体
__block_impl
のisa
ポインタはNSConcreteGlobalBlock
を指す.例2:main関数でblockを作成しようとしたが、blockは変数をキャプチャしなかったが、clang変換によりisaポインタがNSConcreteStackBlockを指していることが分かった.この点はおかしい.「ocプレミアムプログラミング」でこんな言葉を見ました.
関数内で広域変数が記述されていない場所でBlock構文を使用する場合でも、Blockが自動変数をキャプチャしない限り、Block用構造体インスタンスをプログラムのデータ領域に設定することができる.
2. NSConcreteStackBlock
スタックに保存されたblockは、関数が戻ると破棄されます.
#include
int main() {
int a = 100;
void (^block2)(void) = ^{
a;
};
return 0;
}
変換後のC++コード:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
構造体
__block_impl
のisa
ポインタはNSConcreteStackBlock
を指す.3. NSConcreteMallocBlock
スタックに保存されたblockは、参照カウントが0の場合に破棄されます.しかしNSConcreteMallocBlockタイプのblockは通常ソースコードに直接現れず、
[block copy]
の場合、スタックにコピーされる.(以下のコードは、Objective-C blockの実装について)static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
// 1
if (!arg) return NULL;
// 2
aBlock = (struct Block_layout *)arg;
// 3
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 4
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// 5
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 6
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// 7
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 8
result->isa = _NSConcreteMallocBlock;
// 9
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
blockがcopyされると、
_Block_copy_internal
メソッドが呼び出され、内部result->isa = _NSConcreteMallocBlock;
に呼び出される.更新(11.03 9:45):
ありがとうございます.彼がくれたソースコードの中で以下のコードを見つけました.
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy.
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
構造体中のflagsによってcopyが判断されることは明らかである.
次にcopyの例を挙げると,ARCではBlockが関数として値を返すときにNSConcreteMallocBlockタイプにコピーされ,本質はcopyメソッドを呼び出すことである.次のコードがあります.
typedef void (^Block)();
Block getBlock() {
char c = 'YQ';
void (^block)() = ^{
printf("%c", e);
};
return block;
}
void main {
Block block = getBlock();
block();
}
ARC環境では、システムが作成されたときに
objc_retainBlock
メソッドを呼び出すため、正常に動作することができますが、objc_retainBlock
メソッドは実際にはBlock_copy
メソッドです.(runtime/objc-arr.mmから)従って本質的には、以上のコードのシステム実装プロセスは、スタックにblock構造体オブジェクトを作成し、Block_copy
を介してスタックにコピーし、その後、スタック上のオブジェクトを自動リリースプールに登録し、このスタック上のオブジェクトに戻るようになる.しかし、MRC環境では、システムが自動的にコピーしないため、クラッシュする.手動コピーが必要:[block copy]
三、Blockのコピー3種類のBlockコピー:NSConcreteGlobalBlockコピー後、何もしません.NSConcreteStackBlockコピー後、スタックからスタックにコピーします.NSConcreteMallocBlockコピー後、参照カウントを1つ加算します.
四、_block変数のコピー
int main()
{
__block int i = 0;
void (^block)(void) = ^{
i = 1;
};
return 0;
}
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_i_0 *i = __cself->i; // bound by ref
(i->__forwarding->i) = 1;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main()
{
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
return 0;
}
使用する場合ブロック修飾子の場合、基本データ型iは
__Block_byref_i_0
構造体に変換される.__Block_byref_i_0
構造体にはisaポインタがあり、これもオブジェクトであることを示しています.blockが変数を変更すると、次のコードが呼び出されます. __Block_byref_i_0 *i = __cself->i; // bound by ref
(i->__forwarding->i) = 1;
ぐるぐる回っているのを見つけて、下に整理します.最も理解できないのは
__forwarding
ポインタです.__forwarding
ポインタは常に自分を指しています.__block int i
がスタック内にあるとき、__forwarding
はスタック内の自分を指す.iがスタックにコピーされると、__forwarding
は、スタック内の自分を指す.『ocプレミアムプログラミング』で述べたように、
__ブロック修飾変数は構造体メンバー変数
__forwarding
で実現できるblock変数はスタック上でもスタック上でも正しくアクセスできます_block変数.五、Blockコピーペア_block変数の影響
に影響
Blockが使用していたら_ブロック変数、ブロックがスタックからスタックにコピーされると、a.スタックの_block変数はスタックにコピーされ、Blockによって保持されます.b.山の中の_block変数はBlockによって保持される.
これはOCの参照カウントメモリ管理と同じです.下にBlockA、BlockB、_block a、 __block b. BlockAがスタック上のaとbを使用している場合、[BlockA copy]がスタック上にコピーされると、a,bも同時にスタック上にコピーされ、スタック上のBlockAはスタック上のa,bを保持する.同様に、BlockA、BlockBはスタック上のaを使用し、[BlockA copy]、[BlockB copy]がスタック上にコピーされると、BlockAとBlockBはスタック上のaを同時に保持する.
4時の
__block int i
を振り返ると、なぜ__forwarding
針があるのか理解できます.次のコードがあります.int main()
{
__block int i = 0;
void (^block)(void) = [^{
++i;
} copy];
++i;
block();
printf("%d", i);
return 0;
}
上のコードをC++に変換すると、
// block ++i
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_i_0 *i = __cself->i; // bound by ref
++(i->__forwarding->i);
}
// main ++i
++(i.__forwarding->i);
いずれも
++(i.__forwarding->i);
であり,すなわち,いずれもスタック中のiを指していることがわかる.したがって出力は2です.六、いくつかのテーマ、ARCとMRC環境の下で運行できるかどうかを判断する
1すべて実行可能
void exampleA() {
char a = 'A';
^{
printf("%cn", a);
}();
}
2 ARC運転可能
void exampleB_addBlockToArray(NSMutableArray *array) {
char b = 'B';
[array addObject:^{
printf("%cn", b);
}];
}
void exampleB() {
NSMutableArray *array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
3すべて実行可能
void exampleC_addBlockToArray(NSMutableArray *array) {
[array addObject:^{
printf("Cn");
}];
}
void exampleC() {
NSMutableArray *array = [NSMutableArray array];
exampleC_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
4 ARC運転可能
typedef void (^dBlock)();
dBlock exampleD_getBlock() {
char d = 'D';
return ^{
printf("%cn", d);
};
}
void exampleD() {
exampleD_getBlock()();
}
5 ARC運転可能
typedef void (^eBlock)();
eBlock exampleE_getBlock() {
char e = 'E';
void (^block)() = ^{
printf("%cn", e);
};
return block;
}
void exampleE() {
eBlock block = exampleE_getBlock();
block();
}
七、更新(11.03 12:00)blockによる各種変数の処理
漫談Block後半を見て、その部分を整理したいと思いました.まずは!取得した変数が
id, NSObject, __attribute__((NSObject)), block
型の場合block修飾変数は_Block_object_assign
メソッドを呼び出す._Block_object_assign
の方法を一番上に書きます!void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
*dest = _Block_copy(object);
break;
...
case BLOCK_FIELD_IS_BYREF:
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
*dest = object;
break;
...
default:
break;
}
}
1.変数が基本データ型無_block
#include
int main() {
int a = 100;
void (^block2)(void) = ^{
a;
};
return 0;
}
blockの構造体内部にint a変数を追加するだけです.修正できません.
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
2.変数が基本データ型であり_block
int main()
{
__block int i = 0;
void (^block)(void) = ^{
i = 1;
};
return 0;
}
あります_block修飾の基本データ型は
__Block_byref_i_0
構造体に変換される.struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
そして彼がコピーされると
_Block_object_assign
メソッドのBLOCK_FIELD_IS_BYREF
caseに入ります case BLOCK_FIELD_IS_BYREF:
*dest = _Block_byref_copy(object);
break;
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
...
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
...
return src->forwarding;
}
コード発見により、
copy
オブジェクトがスタック上に作成された.そして copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
常に山の上の自分を指さすようにします.
3.変数がオブジェクトなし_block
BLOCK_FIELD_IS_OBJECT
に入りますcase BLOCK_FIELD_IS_OBJECT:
_Block_retain_object(object);
*dest = object;
break;
static void _Block_retain_object_default(const void *ptr __unused) { }
// _Block_retain_object _Block_retain_object_default,
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
// Called from CF to indicate MRR. Newer version uses a versioned structure, so we can add more functions
// without defining a new entry point.
void _Block_use_RR2(const Block_callbacks_RR *callbacks) {
_Block_retain_object = callbacks->retain;
_Block_release_object = callbacks->release;
_Block_destructInstance = callbacks->destructInstance;
}
デフォルトの
_Block_retain_object
は、_Block_retain_object_default
に割り当てられています.つまり、何もしません.すなわち,ARC環境ではBlockがここでオブジェクトを持つことはない.(ARC環境ではより完全なメモリ管理が行われており、外部変数が__strong、copy、strong
で修飾されると、Blockはキャプチャされた変数を__strongで修飾して保持する目的を達成する.)MRR環境では、_Block_retain_object
でid, NSObject, __attribute__((NSObject))
型変数をBlockが保持する.4.変数がオブジェクトにあります_block
BLOCK_FIELD_IS_OBJECT
に入ります case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
*dest = object;
break;
直接付与.だからblock修飾は
_Block_retain_object
メソッドへの呼び出しを避けることができ,すなわちMRR環境下で_blockは、Blockが変数を強く持つことを回避し、循環参照を回避する.