预览图
block 有哪几种类型
共5种,,iOS上基本只有3种 * _NSConcreteGlobalBlock 数据区, 全局定义的block,比如typedef void(^block)(int a);、新创建的block内部没有引用外部变量的在全局区 * _NSConcreteStackBlock 栈区, 初始化的block、 rac下 被赋值后,都会变成malloc * _NSConcreteMallocBlock 堆区, 属性、copy过的,block有引用外部变量的 都是
block 的结构 (Clang后的对应)
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};
- block也有isa指针
- block也有引用计数
- block的invoke是C语言的匿名函数,也可以理解为函 数指针,指向block的实际执行体
- Block_descriptor有三个,分别包含了不同的信息
- block的flags里边会存储block的信息,包含引用计数、是否有签名BLOCK_HAS_SIGNATURE等。
- block的签名很关键,没有签名则无法使用NSInvocation来执行。
将block的descriptor合并后,转换以后的结构如下,比如Aspects里,对block进行invoke调用(hook)
// Block internals.
typedef NS_OPTIONS(int, AspectBlockFlags) {
AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
AspectBlockFlagsHasSignature = (1 << 30)
};
typedef struct _AspectBlock {
__unused Class isa;
AspectBlockFlags flags;
__unused int reserved;
void (__unused *invoke)(struct _AspectBlock *block, ...);
struct {
unsigned long int reserved;
unsigned long int size;
// requires AspectBlockFlagsHasCopyDisposeHelpers
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
// requires AspectBlockFlagsHasSignature
const char *signature;
const char *layout;
} *descriptor;
// imported variables
} *AspectBlockRef;
- (void)testBlock {
void(^block1)(void) = ^{
NSLog(@"block1");
};
block1();
/// block的源码结构:
struct _AspectBlock *myBlock = (__bridge struct _AspectBlock *)block1;
myBlock->invoke(myBlock); // 输出block1
}
获取block的签名
获取到block的底层结构,根据flags 做&操作,然后再进行内存偏移。 代码如下:
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
// Checks for a valid signature, not merely the BLOCK_HAS_SIGNATURE bit.
BLOCK_EXPORT
bool _Block_has_signature(void *aBlock) {
return _Block_signature(aBlock) ? true : false;
}
BLOCK_EXPORT
const char * _Block_signature(void *aBlock)
{
struct Block_descriptor_3 *desc3 = _Block_descriptor_3(aBlock);
if (!desc3) return NULL;
return desc3->signature;
}
/// 测试
- (void)testBlock {
void(^block1)(void) = ^{
NSLog(@"block1");
};
/// 如何通过NSInvocation来执行一个block,关键就在于获取block的方法签名
NSMethodSignature *sign = [self blockSignature:block1];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sign];
invocation.target = block1;
[invocation invoke]; // 输出block1
}
- (NSMethodSignature *)blockSignature:(id)block {
const char *sign = _Block_signature((__bridge void *)block);
return [NSMethodSignature signatureWithObjCTypes:sign];
}
block捕获(多次嵌套情况)
- static 全局变量 存在 __DATA下,存在全局存储区,该地址在程序运行过程中一直不会改变,所以能访问最新值
- const 存在__Text下
- 局部变量 auto变量(普通变量) - 值传递。 被static修饰后,指针传递,
- 全局变量 - 直接访问
block copy 引用外部对象时的过程
如果是对象类型,则转成block后desc对象里会多两个函数指针(即desc2),copy、dispose两个函数指针,时机如下图
MRC下 栈block 不会对持有对象的对象进行copy
block copy到堆或释放的过程
Block_copy
void *_Block_copy(const void *arg) {
//1、
struct Block_layout *aBlock;
if (!arg) return NULL;
//2、
// The following would be better done as a switch statement
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;
}
else {
//5、
// Its a stack block. Make a copy.
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return NULL;
//6、
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
//7、
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
//8、
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
//9、
result->isa = _NSConcreteMallocBlock;
return result;
}
}
static int32_t latching_incr_int(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return BLOCK_REFCOUNT_MASK;
}
if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
return old_value+2;
}
}
}
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->copy)(result, aBlock); // do fixup
}
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
/*
1、先声明一个Block_layout结构体类型的指针,如果传入的Block为NULL就直接返回。
2、如果Block有值就强转成Block_layout的指针类型。
3、如果Block的flags表明该Block为堆Block时,就对其引用计数递增,然后返回原Block。latching_incr_int这个函数进行了一次死循环,如果flags含有BLOCK_REFCOUNT_MASK证明其引用计数达到最大,直接返回,需要三万多个指针指向,正常情况下不会出现。随后做一次原子性判断其值当前是否被其他线程改动,如果被改动就进入下一次循环直到改动结束后赋值。OSAtomicCompareAndSwapInt的作用就是在where取值与old_value相同时,将old_value+2赋给where。 注:Block的引用计数以flags的后16位代表,以 2为单位,每次递增2,1被BLOCK_DEALLOCATING正在释放占用。
4、如果Block为全局Block就不做其他处理直接返回。
5、该else中就是栈Block了,按原Block的内存大小分配一块相同大小的内存,如果失败就返回NULL。
6、memmove()用于复制位元,将aBlock的所有信息copy到result的位置上。
7、将新Block的引用计数置零。BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING就是0xffff,~(0xffff)就是0x0000,result->flags与0x0000与等就将result->flags的后16位置零。然后将新Block标识为堆Block并将其引用计数置为2。
8、如果有copy_dispose助手,就执行Block的保存的copy函数,就是上面的__main_block_copy_0。在_Block_descriptor_2函数中,用BLOCK_HAS_COPY_DISPOSE来判断是否有Block_descriptor_2,且取Block的Block_descriptor_2时,因为有无是编译器确定的,在Block结构体中并无保留,所以需要使用指针相加的方式来确定其指针位置。有就执行,没有就return掉。
9、将堆Block的isa指针置为_NSConcreteMallocBlock,返回新Block,end。
*/
release Block_release 调用 _Block_release
void _Block_release(const void *arg) {
//1、
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
//2、
if (aBlock->flags & BLOCK_IS_GLOBAL) return;
//3、
if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
//4、
if (latching_decr_int_should_deallocate(&aBlock->flags)) {
//5、
_Block_call_dispose_helper(aBlock);
//6、
_Block_destructInstance(aBlock);
//7、
free(aBlock);
}
}
static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return false; // latched high
}
if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
return false; // underflow, latch low
}
int32_t new_value = old_value - 2;
bool result = false;
if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) {
new_value = old_value - 1;
result = true;
}
if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) {
return result;
}
}
}
static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->dispose)(aBlock);
}
static void _Block_destructInstance_default(const void *aBlock __unused) {}
static void (*_Block_destructInstance) (const void *aBlock) = _Block_destructInstance_default;
/*
1、将入参arg强转成(struct Block_layout *)类型,如果入参为NULL则直接返回。
2、如果入参为全局Block则返回不做处理。
3、如果入参不为堆Block则返回不做处理。
4、判断aBlock的引用计数是否需要释放内存。与copy同样的,latching_decr_int_should_deallocate也做了一次循环和原子性判断保证原子性。如果该block的引用计数过高(0xfffe)或者过低(0)返回false不做处理。如果其引用计数为2,则将其引用计数-1即BLOCK_DEALLOCATING标明正在释放,返回true,如果大于2则将其引用计数-2并返回false。
5、如果步骤4标明了该block需要被释放,就进入步骤5。如果aBlock含有copy_dispose助手就执行aBlock中的dispose函数,与copy中的对应不再多做解释。
6、_Block_destructInstance默认没做其他操作,可见源码。
7、最后一步释放掉aBlock,end。
*/
捕获外部变量对象时候执行的方法。真正retain还是用的arc
_Block_object_assign
_Block_object_dispose
_Block_byref_release(__Block引用生成的block)
如果是嵌套block
_Block_release(object)
普通捕获对象,交给arc
__block的作用和结构
- 在block内访问外部变量都是复制进去的,即:写操作不对原变量生效
使用__block标记的变量,会在block内部生成一个结构体,该结构体内创建一个一样的对象,然后进行值传递或指针传递, 这个结构体如下 block内部修改的值,其实就是修改的这个结构体里面的, (里面的和外面被__Block表示的是同一个)
如果引用是对象类型, 结构体里会多上两个函数指针, copy和dispose, 如下:
struct __Block_byref_array_1 {
void *__isa;
__Block_byref_array_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSMutableArray *array;
};
__forwarding指针的作用
结构体中有一个__forwarding指针,初始化时此指针指向转换后变量本身 此后代码中涉及到原变量的地方,都会转换成新变量->__forwarding->原变量同类型变量
- 在使用__block变量时经转换后,其实都是通过其__forwarding来访问的
- block中修改了__block变量,block外修改亦有效,其实这也是__forwarding的功效
block怎么持有对象
FBRetainCycleDetector源码
- (NSSet *)allRetainedObjects {
NSMutableArray *results = [[[super allRetainedObjects] allObjects] mutableCopy];
__attribute__((objc_precise_lifetime)) id anObject = self.object;
void *blockObjectReference = (__bridge void *)anObject;
NSArray *allRetainedReferences = FBGetBlockStrongReferences(blockObjectReference);
for (id object in allRetainedReferences) {
FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self, object, self.configuration);
if (element) {
[results addObject:element];
}
}
return [NSSet setWithArray:results];
}
NSArray *FBGetBlockStrongReferences(void *block) {
if (!FBObjectIsBlock(block)) {
return nil;
}
NSMutableArray *results = [NSMutableArray new];
void **blockReference = block;
NSIndexSet *strongLayout = _GetBlockStrongLayout(block);
[strongLayout enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
void **reference = &blockReference[idx];
if (reference && (*reference)) {
id object = (id)(*reference);
if (object) {
[results addObject:object];
}
}
}];
return [results autorelease];
}
FBGetBlockStrongReferences 是对另一个私有函数 _GetBlockStrongLayout 的封装,也是实现最有意思的部分。
static NSIndexSet *_GetBlockStrongLayout(void *block) {
struct BlockLiteral *blockLiteral = block;
// 如果 block 有 Cpp 的构造器/析构器,说明它持有的对象很有可能没有按照指针大小对齐,我们很难检测到所有的对象
// 如果 block 没有 dispose 函数,说明它无法 retain 对象,也就是说我们也没有办法测试其强引用了哪些对象
if ((blockLiteral->flags & BLOCK_HAS_CTOR)
|| !(blockLiteral->flags & BLOCK_HAS_COPY_DISPOSE)) {
return nil;
}
...
/*
1. 从 BlockDescriptor 取出 dispose_helper 以及 size(block 持有的所有对象的大小)
2. 通过 (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize 向上取整,获取 block 持有的指针的数量
3. 初始化两个包含 elements 个 FBBlockStrongRelationDetector 实例的数组,其中第一个数组用于传入 dispose_helper,第二个数组用于检测 _strong 是否被标记为 YES
4. 在自动释放池中执行 dispose_helper(obj),释放 block 持有的对象
/*
void (*dispose_helper)(void *src) = blockLiteral->descriptor->dispose_helper;
const size_t ptrSize = sizeof(void *);
const size_t elements = (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize;
void *obj[elements];
void *detectors[elements];
for (size_t i = 0; i < elements; ++i) {
FBBlockStrongRelationDetector *detector = [FBBlockStrongRelationDetector new];
obj[i] = detectors[i] = detector;
}
@autoreleasepool {
dispose_helper(obj);
}
...
// 因为 dispose_helper 只会调用 release 方法,但是这并不会导致我们的 FBBlockStrongRelationDetector 实例被释放掉,反而会标记 _strong 属性,在这里我们只需要判断这个属性的真假,将对应索引加入数组,最后再调用 trueRelease 真正的释放对象。
NSMutableIndexSet *layout = [NSMutableIndexSet indexSet];
for (size_t i = 0; i < elements; ++i) {
FBBlockStrongRelationDetector *detector = (FBBlockStrongRelationDetector *)(detectors[i]);
if (detector.isStrong) {
[layout addIndex:i];
}
[detector trueRelease];
}
return layout;
}
实验代码如下
NSObject *firstObject = [NSObject new];
__attribute__((objc_precise_lifetime)) NSObject *object = [NSObject new];
__weak NSObject *secondObject = object;
NSObject *thirdObject = [NSObject new];
__unused void (^block)() = ^{
__unused NSObject *first = firstObject;
__unused NSObject *second = secondObject;
__unused NSObject *third = thirdObject;
};
首先打印 block 变量的大小,因为 block 变量其实只是一个指向结构体的指针,所以大小为 8,而结构体的大小为 32; 以 block 的地址为基址,偏移 32,得到一个指针 使用 $3[0] $3[1] $3[2] 依次打印地址为 0x1001023b0 0x1001023b8 0x1001023c0 的内容,可以发现它们就是 block 捕获的全部引用,前两个是强引用,最后的是弱引用 这可以得出一个结论:block 将其捕获的引用存放在结构体的下面 block 对持有的对象的布局的顺序依然是强引用在前、弱引用在后,我们不妨做一个假设:block 会将强引用的对象排放在弱引用对象的前面
第二个需要介绍的是 dispose_helper,这是 BlockDescriptor 结构体中的一个指针:
struct BlockDescriptor {
unsigned long int reserved; // NULL
unsigned long int size;
// optional helper functions
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
void (*dispose_helper)(void *src); // IFF (1<<25)
const char *signature; // IFF (1<<30)
};
上面的结构体中有两个函数指针,copy_helper 用于 block 的拷贝,dispose_helper 用于 block 的 dispose 也就是 block 在析构的时候会调用这个函数指针,销毁自己持有的对象,而这个原理也是区别强弱引用的关键,因为在 dispose_helper 会对强引用发送 release 消息,对弱引用不会做任何的处理。
私有方法 _GetBlockStrongLayout 进行分析 ,见上面代码
如果 block 有 Cpp 的构造器/析构器,说明它持有的对象很有可能没有按照指针大小对齐,我们很难检测到所有的对象 如果 block 没有 dispose 函数,说明它无法 retain 对象,也就是说我们也没有办法测试其强引用了哪些对象
然后看上述代码_GetBlockStrongLayout的备注
参考文档:https://github.com/draveness/analyze/blob/master/contents/FBRetainCycleDetector/iOS%20%E4%B8%AD%E7%9A%84%20block%20%E6%98%AF%E5%A6%82%E4%BD%95%E6%8C%81%E6%9C%89%E5%AF%B9%E8%B1%A1%E7%9A%84.md
block hook的实现
见上述的获取签名