现在GCD几乎已经是iOS中多线程编程的核心技术,而其中很重要的一个部分就是block的使用,那么用了这么多年的block,block究竟是个啥?最近通过阅读Matt Galloway的博客,现将对block的一些探究翻译并分享出来。
尝试从汇编的角度查看block是啥
首先,建立一个InsideBlock.m文件,代码如下:
typedef void(^BlockA)(void);
void runBlockA(BlockA block) {
block();
}
void doBlockA() {
BlockA block = ^{
// Empty block
};
runBlockA(block);
}
然后使用如下命令,既可以得到该文件的汇编指令:
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.0.sdk -arch armv7 -S -o3 InsideBlock.m -o InsideBlock.s
查看InsideBlock.s文件,从中我们可以先看到runBlock方法的汇编指令:
_runBlockA:
@ BB#0:
push {r7, lr}
mov r7, sp
sub sp, #8
str r0, [sp, #4]
ldr r0, [sp, #4]
mov r1, r0
ldr r0, [r0, #12]
str r0, [sp] @ 4-byte Spill
mov r0, r1
ldr r1, [sp] @ 4-byte Reload
blx r1
add sp, #8
pop {r7, pc}
.globl _doBlockA
.p2align 1
.code 16 @ @doBlockA
.thumb_func _doBlockA
我们不需要详细解读这段汇编,只需要大概了解意思即可,iOS arm汇编中通常用r0-r3表示参数,其中blx表示执行函数调用,也就是说上面的这段汇编单代码就是执行了传入的函数。
再往下是doBlockA的汇编指令:
_doBlockA:
@ BB#0:
push {r7, lr}
mov r7, sp
sub sp, #4
movw r0, :lower16:(___block_literal_global-(LPC1_0+4))
movt r0, :upper16:(___block_literal_global-(LPC1_0+4))
LPC1_0:
add r0, pc
str r0, [sp]
ldr r0, [sp]
bl _runBlockA
add sp, #4
pop {r7, pc}
.p2align 1
.code 16 @ @__doBlockA_block_invoke
.thumb_func ___doBlockA_block_invoke
其中movw和movt是将指付给r0,bl调用了runBlockA方法,因为我们知道runBlockA方法需要一个block对象,因此我们断定:___block_literal_global就是这个block对象的表示。剩下的就是找到___block_literal_global的定义就可以知道block在汇编中是如何表示的了。继续向下找,可以找到:
___block_literal_global:
.long __NSConcreteGlobalBlock
.long 1342177280 @ 0x50000000
.long 0 @ 0x0
.long ___doBlockA_block_invoke
.long ___block_descriptor_tmp
.section __DATA,__objc_imageinfo,regular,no_dead_strip
可以看到它是一个结构体,每个字段四个字节,最后两个字段___doBlockA_block_invoke和___block_descriptor_tmp的定义也可以从汇编中看得到定义:
___doBlockA_block_invoke:
@ BB#0:
sub sp, #8
str r0, [sp, #4]
str r0, [sp]
add sp, #8
bx lr
.section __TEXT,__cstring,cstring_literals
___block_descriptor_tmp:
.long 0 @ 0x0
.long 20 @ 0x14
.long L_.str
.long 0
.p2align 2 @ @__block_literal_global
现在block的结构式比较清楚了,一个结构体,其中里面还有两个字段指向另外的结构体。那么再具体点,我们可以通过查阅compiler-rt项目源码中的Block_private.h头文件,可以看到block里面具体是个啥:
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
那么把所有对刚才汇编的分解综合起来,之前的object-c的block代码就可以用如下比较简单的伪代码来表示出来:
void runBlockA(struct Block_layout *block) {
block->invoke();
}
void block_invoke(struct Block_layout *block) {
// Empty block function
}
void doBlockA() {
struct Block_descriptor descriptor;
descriptor->reserved = 0;
descriptor->size = 20;
descriptor->copy = NULL;
descriptor->dispose = NULL;
struct Block_layout block;
block->isa = _NSConcreteGlobalBlock;
block->flags = 1342177280;
block->reserved = 0;
block->invoke = block_invoke;
block->descriptor = descriptor;
runBlockA(&block);
}
所以归根结底,block在内存中是一个Block_layout的结构体,包含了isa、flgas、reversed、invoke指针和一个descriptor指针
网友评论