Block的类型
Block在日常开发中常见的有三种类型
__NSGlobalBlock__(全局Block)
__NSStackBlock__(栈Block)
__NSMallocBlock__(堆Block)
我们可以测试一下

在Block源码中我们可以看到还有
_NSConcreteFinalizingBlock,_NSConcreteAutoBlock,_NSConcreteWeakBlockVariable类型的Block,但是我们开发中不会遇到,就不去讨论了
Block到底是个什么?
我们已经知道Block是分为三种类型,但是Block到底是个什么东西?
我们在main.m中写个最简单的Block
int main(int argc, const char * argv[]) {
id block = ^{};
return 0;
}
使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m编译成C++代码,删掉无用代码后

main函数去掉各种强转后变成
int main(int argc, const char * argv[]) {
id block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
return 0;
}
在main中调用了__main_block_impl_0方法,并传入了__main_block_func_0与&__main_block_desc_0_DATA两个参数从上面代码中可以看到__main_block_impl_0是一个结构体,而__main_block_impl_0()是同名的构造函数,而在这个函数中主要是给Block内部的成员赋值,也可以看到Block也含有一个isa指针,从先前的文章我们可以了解到所有包含isa指针的都是一个OC对象。然后我们看到__main_block_impl_0结构体中包含了__block_impl结构体:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
- isa 指针,指向类
- Flags 标志变量,在实现block的内部操作时会用到
- Reserved 保留变量
- FuncPtr 函数指针,Block调用的就是这个函数指针
还包含了__main_block_desc_0结构体
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
- reserved 保留变量
- Block_size Block的大小
所以在我们声明一个Block的时候其实是生成了一个结构体,并将函数指针与__main_block_desc_0结构体一并传入
在Block源码中我们可以看到Block的真正结构体
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
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
};
与我们编译出来的C++代码除了部分成员名称不一样,剩下是完全一样的
ARC中Block的Copy
在以前MRC时代Block的赋值都需要使用[block copy],这是因为生成的Block(^{})是在栈上的,栈上的Block的释放不归我们管,所以需要使用copy将Block拷贝到堆上,这样可以由我们自己手动的去管理内存;ARC时代后编译器对我们进行了优化,声明一个Block内部使用了非静态变量并且将这个Block赋值给一个强指针时会自动的copy到堆上。
在在Block源码中的runtime.c文件中可以找到如下方法
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy.
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
在最下面的那个else里面可以看到栈上Block拷贝到堆上的操作,首先先以Block的大小创建一个新的Block结构体,然后将栈上Block的内容赋值给堆上的Block,接着修改flags,isa的值,所以,copy到堆上的Block除了静态区域的Block_descriptor_1与void (*invoke)(void *, ...);指针,剩下的值都从栈copy 到堆
_Block_call_copy_helper方法是对Block中所持有的变量进行copy 操作的,暂且不表,下面会说
Block对变量的捕捉
Block捕获基础数据类型
我们都知道这么写是会报错的,那么究竟是为什么会报错?

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m转成C++,删除无用代码

因为在Block中直接修改
i值会报错,所以讲Block中代码修改为NSlog
可以看到,这次编译出来的代码与上一次编译出来的有了一些不一样的地方
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int i;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_impl_0结构体中多了个int i成员,同时,在main方法中
int main(int argc, const char * argv[]) {
int i = 10;
id block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, i));
return 0;
}
是将i传入到Block中,传入的是个值类型,所以在我们block内部修改的时候会报错
Block捕获OC对象类型
那么Block内部使用的是对象类型会是什么样?
在main编辑为
int main(int argc, const char * argv[]) {
int i = 10;
id objc = [NSObject new];
id block = ^{ NSLog(@"%@",objc);};
return 0;
}
编译后

__main_block_desc_0结构体中多了两个函数指针
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
并从初始化的地方可以看到,这两个指针指向的方法是
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign( (void*)&dst->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
_Block_object_dispose((void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
上面我们已经说了,ARC环境下会将栈上的Block拷贝到堆上,但是我们有个方法还没说
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;
}
这个方法是从Block中根据偏移获取Block_descriptor_2结构体
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
};
调用(*desc->copy)(result, aBlock);则是可以在编译的C++代码中分析到正是__main_block_copy_0方法,传入的参数第一个是堆上的Block,第二份参数是栈上的Block,而__main_block_copy_0方法调用了_Block_object_assign方法
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
default:
break;
}
}
在__main_block_copy_0中_Block_object_assign( (void*)&dst->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);传入的枚举值是BLOCK_FIELD_IS_OBJECT,所以调用的是_Block_retain_object(object);方法
void _Block_use_RR2(const Block_callbacks_RR *callbacks) {
_Block_retain_object = callbacks->retain;
_Block_release_object = callbacks->release;
_Block_destructInstance = callbacks->destructInstance;
}
而_Block_retain_object则是retain指针,继续找,在libdispatch源码中中可以看到
void
_os_object_init(void)
{
_objc_init();
Block_callbacks_RR callbacks = {
sizeof(Block_callbacks_RR),
(void (*)(const void *))&objc_retain,
(void (*)(const void *))&objc_release,
(void (*)(const void *))&_os_objc_destructInstance
};
_Block_use_RR2(&callbacks);
#if DISPATCH_COCOA_COMPAT
const char *v = getenv("OBJC_DEBUG_MISSING_POOLS");
_os_object_debug_missing_pools = v && !strcmp(v, "YES");
#endif
}
void*
os_retain(void *obj)
{
return objc_retain(obj);
}
最终还是调用的是objc4源码中的objc_retain方法,所以Block捕获的是OC对象时,只是对这个对象进行了retain操作
_os_object_init方法是在_objc_init之前调用的
在block销毁的时候会调用_Block_object_dispose进行销毁操作,这里会对应的做出release操作
Block捕获__block修饰的基础数据类型
我们都知道如果在Block中想要修改一个局部变量的值需要使用__block去修饰,那么为什么使用__block修饰后就可以修改了?我们写如下代码
int main(int argc, const char * argv[]) {
__block int i = 10;
id block = ^{
i = 100;
NSLog(@"%d",i);
};
return 0;
}
编译成C++

__Block_byref_i_0结构体
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
我们用__block修饰的int i是它的一个成员,所以我们使用__block修饰一个变量后会被编译器自动的编译成一个结构体,因为成员变成了一个结构体,所以修改结构体中一个成员是不会有问题的。
__Block_byref_i_0结构体中有一个__Block_byref_i_0 *__forwarding;指针,在main函数里面设置这个指针指向自身。同样__main_block_desc_0包含了copy与dispose方法,但是传入的枚举值是BLOCK_FIELD_IS_BYREF,在_Block_object_assign方法中对应
*dest = _Block_byref_copy(object);
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
//根据栈上的Block_byref大小创建个堆上的Block_byref结构体
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
代码中有注释,如果src指针在栈上时走if((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0)分支,可以从这段代码上看出来在Block从栈上拷贝到堆上时会在堆上创建一个Block_byref结构体,并将栈上的forwarding与堆上新创建的forwarding都指向堆上的Block_byref,所以在Block里面改变了__block修饰的变量的值后外部也会跟着一块被修改。相对应的_Block_object_dispose方法会调用_Block_byref_release方法将堆上的Block_byref释放
还有个(*src2->byref_keep)(copy, src);,这个我们下面来分析
Block捕获__block修饰的OC对象类型
不多说,先编写一段代码在转成C++
int main(int argc, const char * argv[]) {
__block NSObject *object = [NSObject new];
id block = ^{
object = [NSObject new];
NSLog(@"%@",object);
};
return 0;
}
因为代码过长,不易截图,我就贴上与前面不一样的部分
struct __Block_byref_object_0 {
void *__isa;//8
__Block_byref_object_0 *__forwarding;//8
int __flags;//4
int __size;//4
void (*__Block_byref_id_object_copy)(void*, void*);//8
void (*__Block_byref_id_object_dispose)(void*);//8
NSObject *object;
};
int main(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_object_0 object = {
(void*)0,
(__Block_byref_object_0 *)&object,
33554432,
sizeof(__Block_byref_object_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))
};
id block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_object_0 *)&object, 570425344));
return 0;
}
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
其他的部分与__block修饰的基础数据类型的代码基本一样,只是在__Block_byref_object_0结构体中多了两个函数指针__Block_byref_id_object_copy与__Block_byref_id_object_dispose。上面说到还有个_Block_byref_copy方法中有个(*src2->byref_keep)(copy, src);没有去分析,现在我们就来看看,首先使用struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);获取到Block_byref_2结构体
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
void (*byref_destroy)(struct Block_byref *);
};
所以byref_keep正是我们传入的__Block_byref_id_object_copy_131方法,这个方法中调用了我们熟悉的_Block_object_assign方法,不过不一样的是这次编码出来的参数看着比较诡异,我们一点点的看
(char*)dst + 40首先,我们可以看到dst是传出的栈上的Block_byref结构体指针地址,而我们__Block_byref_object_0前面固定的成员所占空间刚好是40,所以(char*)dst + 40是拿到NSObject *object指针;相对的*(void * *) ((char*)src + 40)是拿到栈上的NSObject *object指针,但是这个前面多了个*所以这是取出这个指针所指向的值;
前面两个分析清楚了,那么131是什么?查看枚举值发现BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT(3 | 128)的值刚好就是131,我们接着去_Block_object_assign方法中去查看
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
*dest = object;
break;
当传入的值是131时,是把栈上的NSObject *object的值赋值给堆上的NSObject *object
当然_Block_object_assign方法中并不简单的只有这四种情况,后面的其他情况就不在去分析了,有兴趣的朋友可以去源码中分析
循环引用
看完上面的朋友们,想必不用我说都知道为什么会有循环引用了,当一个对象持有block,而且这个block也持有这个对象时就会造成循环引用,那么怎么解除循环引用我们也很清楚只要使用__weak就可以解决了,不过现在还有种比较神奇的解决方案,使用__block
__block Person * block_self = self;
self.block = ^{
NSLog(@"%@",block_self);
block_self = nil;
};
self.block();
一定要调用这个block,不然
block_self不会被置为nil,还是没有打破循环引用
最后一提
而Masonry的Block为什么可以在里面直接使用self?
[self.tableview mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.bottom.mas_equalTo(-30.f);
}];
点进源码中可以看到
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
self并没有持有或间接持有block,所以不会造成循环引用,同理[UIView animateWithDuration:animations:]可以在内部使用block是一样的道理
疑惑
本来把block看完了,以为对block已经算是有些了解了,但是看到孙源大神在微博上发的题发现自己还是太天真了

struct YTY_Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
struct YTY_Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
};
void hookInvokePrintHelloWorld(void *obj, ...){
printf("Hello,world\n");
}
void HookBlockToPrintHelloWorld(id block){
struct YTY_Block_layout *yty_block = (__bridge struct YTY_Block_layout *)block;
yty_block->invoke = &hookInvokePrintHelloWorld;
((__bridge struct YTY_Block_layout *)block)->invoke = &hookInvokePrintHelloWorld;
}
但是第二题,第三题实在搞不定,尝试过使用method swizzling去替换NSBlock中的- (void)invoke方法,方法能替换但是Block不会调用。不知道有没有大神能帮忙解答下这几个问题
文章参考:
破弓的Block专题
Block源码