iOS 动态调用Block
关于block 内部实现 ,大多数博客都是使用 clang -rewrite-objc main.m
来查看c++源码:
struct __Person__init_block_impl_0 {
struct __block_impl impl;
struct __Person__init_block_desc_0* Desc;
Person *self;
__Person__init_block_impl_0(void *fp, struct __Person__init_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static struct __Person__init_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __Person__init_block_impl_0*, struct __Person__init_block_impl_0*);
void (*dispose)(struct __Person__init_block_impl_0*);
} __Person__init_block_desc_0_DATA = { 0, sizeof(struct __Person__init_block_impl_0), __Person__init_block_copy_0, __Person__init_block_dispose_0};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
解析出来的c++ 源码,其中block 由一个结构体实现。很多文章都有解析block 的,但是基本上都是说 __block_impl-> Flags 未预留字段,由于没有实际用到,基本也不会深究,直到看到AOP库Aspects和RunTime库CTObjectiveCRuntimeAdditions还有JavascriptCore才发现,原来block可以用NSInvocation来动态调用。
- 用过NSInvocation的应该知道, 它可以用来实现对指定对象的任意方法的调用
- 使用NSInvocation调用block 流程 并没有selector, 调用逻辑是:
获取block的signature->设置target为block->设置参数->invoke 并不需要想普通方法一样需要selector - NSInvocation 通过NSMethodSignature 来创建
那么通过以上几点点,使用NSInvocation我们就需要获取block的Signature,在JavascriptCore中使用了 _Block_has_signature 和 _Block_signature来获取Signature,但是这两个函数又属于私有API,而Aspects和CTObjectiveCRuntimeAdditions中都是使用block 结构体中的字段来实现的,但是又发现他们定义的结构和clang -rewrite-objc main.m
解析出来的不大一样。直到看到了Clang 7 文档 才发现他们使用的block和这上面的一模一样。
struct Block_literal_1 {
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 {
unsigned long int reserved; // NULL
unsigned long int size; // sizeof(struct Block_literal_1)
// optional helper functions
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
void (*dispose_helper)(void *src); // IFF (1<<25)
// required ABI.2010.3.16
const char *signature; // IFF (1<<30)
} *descriptor;
// imported variables
};
他们用到了其中的flags和signature,其中flags是一个枚举值
enum {
BLOCK_HAS_COPY_DISPOSE = (1 << 25),
BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code
BLOCK_IS_GLOBAL = (1 << 28),
BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30),
};
结合上面block发现 flags 标识 Block_descriptor_1结构体中的结构
struct CTBlockLiteral *blockRef = (__bridge struct CTBlockLiteral *)block;
_flags = blockRef->flags;
_size = blockRef->descriptor->size;
if (_flags & CTBlockDescriptionFlagsHasSignature) {
void *signatureLocation = blockRef->descriptor;
signatureLocation += sizeof(unsigned long int);
signatureLocation += sizeof(unsigned long int);
if (_flags & CTBlockDescriptionFlagsHasCopyDispose) {
signatureLocation += sizeof(void(*)(void *dst, void *src));
signatureLocation += sizeof(void (*)(void *src));
}
const char *signature = (*(const char **)signatureLocation);
_blockSignature = [NSMethodSignature signatureWithObjCTypes:signature];
}
上面为CTObjectiveCRuntimeAdditions的代码与Aspects实现基本相同。
都是通过将block转换为自定义与block相同结构的结构体。由于结构体中Block_descriptor_1结构不固定,所以只有通过指针从首地址进行偏移来获取到最后的signature;
然后使用signature创建NSInvocation,然后设置Target为block,设置参数然后调用invoke。
参考文档
clang7文档
NSInvocation动态调用任意block
CTObjectiveCRuntimeAdditions
Aspects