Block 原理探究代碼篇
demo 我已經(jīng)放在了 github bytedance-alibaba-interview,注意下 ARC 和 MRC 內(nèi)存管理可能會得出不同的結(jié)論。
首先明確 Block 底層數(shù)據(jù)結(jié)構(gòu),之后所有的 demos 都基于此來學習知識點:
typedef NS_OPTIONS(int,PTBlockFlags) {
PTBlockFlagsHasCopyDisposeHelpers = (1 << 25),
PTBlockFlagsHasSignature = (1 << 30)
};
typedef struct PTBlock {
__unused Class isa;
PTBlockFlags flags;
__unused int reserved;
void (__unused *invoke)(struct PTBlock *block, ...);
struct {
unsigned long int reserved;
unsigned long int size;
// requires PTBlockFlagsHasCopyDisposeHelpers
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
// requires PTBlockFlagsHasSignature
const char *signature;
const char *layout;
} *descriptor;
// imported variables
// Block 捕獲的實例變量都在次
} *PTBlockRef;
typedef struct PTBlock_byref {
void *isa;
struct PTBlock_byref *forwarding;
volatile int flags; // contains ref count
unsigned int size;
// 下面兩個函數(shù)指針是不定的 要根據(jù)flags來
// void (*byref_keep)(struct PTBlock_byref *dst, struct PTBlock_byref *src);
// void (*byref_destroy)(struct PTBlock_byref *);
// long shared[0];
} *PTBlock_byref_Ref;
1. 調(diào)用 block
void (^blk)(void) = ^{
NSLog(@"hello world");
};
PTBlockRef block = (__bridge PTBlockRef)blk;
block->invoke(block);
2. block 函數(shù)簽名
void (^blk)(int, short, NSString *) = ^(int a, short b, NSString *str){
NSLog(@"a:%d b:%d str:%@",a,b,str);
};
PTBlockRef block = (__bridge PTBlockRef)blk;
if (block->flags & PTBlockFlagsHasSignature) {
void *desc = block->descriptor;
desc += 2 * sizeof(unsigned long int);
if (block->flags & PTBlockFlagsHasCopyDisposeHelpers) {
desc += 2 * sizeof(void *);
}
const char *signature = (*(const char **)desc);
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:signature];
NSLog(@"方法 signature:%s",signature);
}
// 打印內(nèi)容如下:
// v24 @?0 i8 s12 @"NSString"16
// 其中 ? 是 An unknown type (among other things, this code is used for function pointers)
3. block 捕獲棧上局部變量
捕獲的變量都會按照順序放置在 PTBlock
結(jié)構(gòu)體后面,如此看來就是個變長結(jié)構(gòu)體。
也就是說我們可以通過如下方式知道 block 捕獲了哪些外部變量(全局變量除外)。
int a = 0x11223344;
int b = 0x55667788;
NSString *str = @"pmst";
void (^blk)(void) = ^{
NSLog(@"a:%d b:%d str:%@",a,b, str);
};
PTBlockRef block = (__bridge PTBlockRef)blk;
void *pt = (void *)block + sizeof(struct PTBlock);
long long *ppt = pt;
NSString *str_ref = (__bridge id)((void *)(*ppt));
int *a_ref = pt + sizeof(NSString *);
int *b_ref = pt + sizeof(NSString *) + sizeof(int);
NSLog(@"a:0x%x b:0x%x str:%@",*a_ref, *b_ref, str_ref);
TODO:
NSString
layout 布局為何在第一位?
4. __block
變量(棧上)
__block int a = 0x99887766;
__unsafe_unretained void (^blk)(void) = ^{
NSLog(@"__block a :%d",a);
};
NSLog(@"Block 類型 %@",[blk class]);
PTBlockRef block = (__bridge PTBlockRef)blk;
void *pt = (void *)block + sizeof(struct PTBlock);
long long *ppt = pt;
void *ref = (PTBlock_byref_Ref)(*ppt);
void *shared = ref + sizeof(struct PTBlock_byref);
int *a_ref = (int *)shared;
NSLog(@"a 指針:%p block a 指針:%p block a value:0x%x",&a, a_ref,*a_ref);
NSLog(@"PTBlock_byref 指針:%p",ref);
NSLog(@"PTBlock_byref forwarding 指針:%p",((PTBlock_byref_Ref)ref)->forwarding);
/*
輸出如下:
Block 類型 __NSStackBlock__
a 指針:0x7ffeefbff528 block a 指針:0x7ffeefbff528 block a value:0x99887766
PTBlock_byref 指針:0x7ffeefbff510
PTBlock_byref forwarding 指針:0x7ffeefbff510
*/
可以看到 __block int a
已經(jīng)變成了另外一個數(shù)據(jù)結(jié)構(gòu)了,打印地址符合預期,此刻 block 以及其他的變量結(jié)構(gòu)體都在棧上。
5. __block
變量,[block copy] 后的內(nèi)存變化
__block int a = 0x99887766;
__unsafe_unretained void (^blk)(NSString *) = ^(NSString *flag){
NSLog(@"[%@] 中 a 地址:%p",flag, &a);
};
NSLog(@"blk 類型 %@",[blk class]);
blk(@"origin block");
void (^copyblk)(NSString *) = [blk copy];
copyblk(@"copy block");
blk(@"origin block 二次調(diào)用");
/**
輸出如下:
blk 類型 __NSStackBlock__
[origin block] 中 a 地址:0x7ffeefbff528
copyblk 類型 __NSMallocBlock__
[copy block] 中 a 地址:0x102212468
[origin block 二次調(diào)用] 中 a 地址:0x102212468
*/
很明顯對 blk 進行 copy 操作后,copyblk 已經(jīng)“移駕”到堆上,隨著拷貝的還有 __block
修飾的a變量(PTBlock_byref_Ref
類型);
6. __block
變量中 forwarding 指針
__block int a = 0x99887766;
__unsafe_unretained void (^blk)(NSString *,id) = ^(NSString *flag, id bblk){
NSLog(@"[%@] a address:%p",flag, &a); // a 取值都是 ->forwarding->a 方式
PTBlockRef block = (__bridge PTBlockRef)bblk;
void *pt = (void *)block + sizeof(struct PTBlock);
long long *ppt = pt;
void *ref = (PTBlock_byref_Ref)(*ppt);
NSLog(@"[%@] PTBlock_byref_Ref 指針:%p",flag,ref);
NSLog(@"[%@] PTBlock_byref_Ref forwarding 指針:%p",flag,((PTBlock_byref_Ref)ref)->forwarding);
void *shared = ref + sizeof(struct PTBlock_byref);
int *a_ref = (int *)shared;
NSLog(@"[%@] a value : 0x%x a adress:%p", flag, *a_ref, a_ref);
};
NSLog(@"blk 類型 %@",[blk class]);
blk(@"origin block", blk);
void (^copyblk)(NSString *,id) = [blk copy];
NSLog(@"copyblk 類型 %@",[copyblk class]);
copyblk(@"copy block",copyblk);
blk(@"origin block after copy", blk);
/**
MRC 模式下輸出:
blk 類型 __NSStackBlock__
[origin block] a address:0x7ffeefbff528
[origin block] PTBlock_byref_Ref 指針:0x7ffeefbff510
[origin block] PTBlock_byref_Ref forwarding 指針:0x7ffeefbff510
[origin block] a value : 0x99887766 a adress:0x7ffeefbff528
copyblk 類型 __NSMallocBlock__
[copy block] a address:0x1032041d8
[copy block] PTBlock_byref_Ref 指針:0x1032041c0
[copy block] PTBlock_byref_Ref forwarding 指針:0x1032041c0
[copy block] a value : 0x99887766 a adress:0x1032041d8
[origin block after copy] a address:0x1032041d8
[origin block after copy] PTBlock_byref_Ref 指針:0x7ffeefbff510
[origin block after copy] PTBlock_byref_Ref forwarding 指針:0x1032041c0
[origin block after copy] a value : 0x99887766 a adress:0x7ffeefbff528
ARC 模式下輸出(這個稍有出路):
blk 類型 __NSStackBlock__
[origin block] a address:0x100604cc8
[origin block] PTBlock_byref_Ref 指針:0x100604cb0
[origin block] PTBlock_byref_Ref forwarding 指針:0x100604cb0
[origin block] a value : 0x99887766 a adress:0x100604cc8
copyblk 類型 __NSMallocBlock__
[copy block] a address:0x100604cc8
[copy block] PTBlock_byref_Ref 指針:0x100604cb0
[copy block] PTBlock_byref_Ref forwarding 指針:0x100604cb0
[copy block] a value : 0x99887766 a adress:0x100604cc8
*/
這里可以看到 forwarding 指針確實指向了結(jié)構(gòu)體本身,隨著 copy 行為確實進行了一次棧->堆的賦值——block
和 __block
變量。
建議用 lldb 命令去看內(nèi)存布局。
- Block Hook
TODO: