iOS Block 相关梳理

预览图

19fb051fafa97c7071b5fbc895f60c9c

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
};

  1. block也有isa指针
  2. block也有引用计数
  3. block的invoke是C语言的匿名函数,也可以理解为函 数指针,指向block的实际执行体
  4. Block_descriptor有三个,分别包含了不同的信息
  5. block的flags里边会存储block的信息,包含引用计数、是否有签名BLOCK_HAS_SIGNATURE等。
  6. 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捕获(多次嵌套情况)

  1. static 全局变量 存在 __DATA下,存在全局存储区,该地址在程序运行过程中一直不会改变,所以能访问最新值
  2. const 存在__Text下
  3. 局部变量 auto变量(普通变量) - 值传递。 被static修饰后,指针传递,
  4. 全局变量 - 直接访问

057b50725476af18687924d0424f2f21

block copy 引用外部对象时的过程

如果是对象类型,则转成block后desc对象里会多两个函数指针(即desc2),copy、dispose两个函数指针,时机如下图 62c7dca715b49e02b9f5473193af95a2

MRC下 栈block 不会对持有对象的对象进行copy

846773118ee852b0456452b59313e4d2

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表示的是同一个) 362ed83b7c20781de1a25f3194cebe0a

如果引用是对象类型, 结构体里会多上两个函数指针, 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->原变量同类型变量

  1. 在使用__block变量时经转换后,其实都是通过其__forwarding来访问的
  2. 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;
};

7e4062511e77b5914fd1a01d043824e1

首先打印 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的实现

见上述的获取签名

如何获取block参数的个数和类型