身为iOS开发人员熟练使用block是必备技能,也是面试中常考的面试点。对于block来说,它即是对象,也是结构体,还可以是代码块,我们可以方便的将其作为参数、函数、属性来使用,也正是他的强大,因此被程序员广泛使用,本文就由浅入深的来探探block。
Block类型
全局Block
- 当block在没有捕获外部的局部变量或者只使用了全局变量或静态变量的情况下,block的类型为
全局Block。
void (^block)(void) = ^{
};
block();
NSLog(@"%@",block);
打印结果如下:
<__NSGlobalBlock__: 0x1097a3050>
那么使用静态变量的情况下,如下:
堆Block
- 当捕获了局部变量并赋值给了
强引用的情况下,block的类型为堆Block。
int a = 1;
void (^block)(void) = ^{
NSLog(@"%d",a);
};
block();
NSLog(@"%@",block);
打印结果如下:
<__NSMallocBlock__: 0x600000a66c40>
栈Block
- 当捕获了局部变量但是赋值给了
弱引用,这时的block的类型为栈Block。
int a = 1;
void (__weak ^block)(void) = ^{
NSLog(@"%d",a);
};
block();
NSLog(@"%@",block);
打印结果如下:
<__NSStackBlock__: 0x7ff7b9ed5f68>
面试题一
看下方代码,打印结果。
NSObject * objc = [[NSObject alloc]init];
NSLog(@"-- %ld",CFGetRetainCount((__bridge CFTypeRef)objc));
void(^block1)(void) = ^{
NSLog(@"-- %ld",CFGetRetainCount((__bridge CFTypeRef)objc));
};
block1();
void(__weak ^block2)(void) = ^{
NSLog(@"-- %ld",CFGetRetainCount((__bridge CFTypeRef)objc));
};
block2();
分析:
这个其实主要看一点,当block从
栈区copy到堆区的时候,捕获变量的引用计数是需要+1的,因此,当对象创建出来,引用计数为1,block1从栈区生成并捕获变量,引用计数又+1,当block1从栈区拷贝到堆区引用计数又+1,然后就是block2在栈上生成并捕获变量导致的+1。
面试题二
那么为什么要copy block呢?
block在创建的时候,他的内存是分配在栈上的;- 栈上的内存由系统来控制,因此创建的对象有可能随时被销毁,销毁后如果再次调用就有可能造成崩溃。
- 但是在对
block进行copy后,block就存放在堆区,他的生命周期就会随着对象的销毁而销毁。 - 另外,在
ARC下,对block使用copy与strong其实是一样的,因为block的retain就是用copy实现的,所以ARC下block使用copy和strong都可以。
block底层
那么block是如何copy的呢?要弄懂这个问题,我们需要首先看下block的结构,我们可以通过clang -rewrite-objc命令可以查看下编译后的block代码。
- 在这里可以看到
block编译成底层后其实是一个__main_block_impl_0的结构体。 - 并且该结构体中有一个
isa,说明block其实也是一个对象; - 在这里也能发现,在编译期传入的isa的值为
_NSConcreteStackBlock的地址,也就是说编译期就已经知道block的类型; - 在调用的地方,有传入的捕获的
obj对象,因此也说明,捕获变量是在编译期。
那么block的底层又是如何copy的呢?我们可以通过汇编来看下调用:
在这里能看到block的底层会调用
objc_retainBlock,然后调用_Block_copy函数。
如果我们要看下_Block_copy函数的具体实现就需要看下libclosure源码
Block_layout
在去了解Block_copy函数之前,我们需要看看下Block_layout,也就是对block的封装的结构体:
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
// libffi ->
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
isa指针指向Block的类型flags是一个标记位,用于标记Block的类型、状态等(具体类型见下方代码);reserved保留字段,源码中未看到有使用invoke是Block函数,Block调用实际上就是调用的invokedescriptor是可变属性
// 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
};
乍看Block_layout结构体就这几个参数,但descriptor是个可变属性,可变在哪里呢?如果我们查看Block_descriptor_1结构体,就会发现其他两个类似的结构体:
#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
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
#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
};
而Block_descriptor_2和Block_descriptor_3就是有可能要导入的参数,这三个结构体中的内容各不相同
Block_descriptor_1存储block的大小Block_descriptor_2里面有block的copy函数和dispose函数Block_descriptor_3里存放了signature签名等信息 如果要看下这三者的联系,可以看下block在copy的时候调用的三个函数:
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;
}
// 取得 block 中的 Block_descriptor_2,它藏在 descriptor 列表中
// 调用者:_Block_call_copy_helper() / _Block_call_dispose_helper
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
// Block_descriptor_2 中存的是 copy/dispose 方法,如果没有指定有 copy / dispose 方法,则返回 NULL
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
// 先取得 Block_descriptor_1 的地址
uint8_t *desc = (uint8_t *)aBlock->descriptor;
// 偏移 Block_descriptor_1 的大小,就是 Block_descriptor_2 的起始地址
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
// 取得 block 中的 Block_descriptor_3,它藏在 descriptor 列表中
// 调用者:_Block_extended_layout() / _Block_layout() / _Block_signature()
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
// Block_descriptor_3 中存的是 block 的签名,如果没有指定有签名,则直接返回 NULL
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
// 先取得 Block_descriptor_1 的地址
uint8_t *desc = (uint8_t *)aBlock->descriptor;
// 先偏移 Block_descriptor_1 的大小
desc += sizeof(struct Block_descriptor_1);
// 如果还有 Block_descriptor_2,就再偏移 Block_descriptor_2 的大小,得到的就是 Block_descriptor_3 的地址
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
通过这函数,也可以看到Block_descriptor_2与Block_descriptor_3的大体结构:
Block_descriptor_1其实就是descriptor,直接返回即可;Block_descriptor_2为偏移Block_descriptor_1大小的地址(Block_descriptor_1与Block_descriptor_2的地址紧挨,Block_descriptor_1的尾地址就是Block_descriptor_2的首地址)Block_descriptor_3的地址需要看下有没有Block_descriptor_2,如果没有,只需要偏移Block_descriptor_1的大小即可,如果有,就需要偏移Block_descriptor_1+Block_descriptor_2的大小。 也就是说着三者在内存上是紧挨着的结构,类似于数组;
我们看下当block在捕获一个常量参数的时候(如下代码),在编译之后生成的cpp文件中,block的结构:
int a = 10;
void (^block)(void) = ^ {
NSLog(@"%d",a);
};
block();
编译后:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
这里的__main_block_impl_0为最终编译后的block类型,在加载的时候,又会被强转成Block_layout类型,并且他们的对应关系为:
| __main_block_impl_0 | 值 | Block_layout |
|---|---|---|
| isa | &_NSConcreteStackBlock | isa |
| Flags | flags=0 | flags |
| FuncPtr | fp闭包地址 | invoke |
| Block_size | sizeof(struct __main_block_impl_0)block大小 | Block_descriptor_1中的size |
这也就是存在Block_descriptor_1结构体的情况;
当block在捕获一个对象参数的时候,如下代码:
NSObject *objc = [NSObject new];
void (^block)(void) = ^ {
NSLog(@"%@",objc);
};
block();
编译后:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *objc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_objc, int flags=0) : objc(_objc) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
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*/);}
通过上下对比是可以看到__main_block_desc_0结构体多了两个参数__main_block_copy_0和__main_block_dispose_0,这两个参数最终会在blockcopy的时候调用_Block_object_assign和_Block_object_dispose这两个函数,也就是前文说到的copy函数和dispose函数,这时对应关系中就变成了:
| __main_block_impl_0 | 值 | Block_layout |
|---|---|---|
| isa | &_NSConcreteStackBlock | isa |
| Flags | flags=0 | flags |
| FuncPtr | fp闭包地址 | invoke |
| Block_size | sizeof(struct __main_block_impl_0)block大小 | Block_descriptor_1中的size |
| *copy | _Block_object_assign | copy函数 |
| *dispose | _Block_object_dispose | dispose函数 |
这是存在Block_descriptor_2结构体的情况;至于Block_descriptor_3,也就是方法签名,其是在运行的时候添加到Block_layout中,因此,可以通过打印来看下:
Block_copy
熟悉了Block_layout的结构组成,接下来就看下_Block_copy函数的调用:
// 拷贝 block,
// 如果原来就在堆上,就将引用计数加 1;
// 如果原来在栈上,会拷贝到堆上,引用计数初始化为 1,并且会调用 copy helper 方法(如果存在的话);
// 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
// 参数 arg 就是 Block_layout 对象,
// 返回值是拷贝后的 block 的地址
// 运行?stack -》malloc
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
// 如果 arg 为 NULL,直接返回 NULL
if (!arg) return NULL;
// The following would be better done as a switch statement
// 强转为 Block_layout 类型
aBlock = (struct Block_layout *)arg;
const char *signature = _Block_descriptor_3(aBlock)->signature;
// 如果现在已经在堆上
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
// 就只将引用计数加 1
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy.
// block 现在在栈上,现在需要将其拷贝到堆上
// 在堆上重新开辟一块和 aBlock 相同大小的内存
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
// 开辟失败,返回 NULL
if (!result) return NULL;
// 将 aBlock 内存上的数据全部复制新开辟的 result 上
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
// 将 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清为 0
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
// 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// copy 方法中会调用做拷贝成员变量的工作
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
// isa 指向 _NSConcreteMallocBlock
result->isa = _NSConcreteMallocBlock;
return result;
}
}
- 这里传入的参数是
block,在进入后,就会如前文中所说,把block强转成Block_layout类型; - 如果
block已经在堆上,就只将引用计数+1 - 如果
block在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身 - 如果
block在栈上,现在需要将其拷贝到堆上,这时就会调用_Block_call_copy_helper函数;
// 调用 block 的 copy helper 方法,即 Block_descriptor_2 中的 copy 方法
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
// 取得 block 中的 Block_descriptor_2
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
// 如果没有 Block_descriptor_2,就直接返回
if (!desc) return;
// 调用 desc 中的 copy 方法,copy 方法中会调用 _Block_object_assign 函数
(*desc->copy)(result, aBlock); // do fixup
}
而_Block_call_copy_helper函数就会调用前文中说到的_Block_descriptor_2函数来获取_Block_descriptor_2结构体,如果有该结构体,然后调用_Block_object_assign函数来处理捕获的参数(前文中的对照表中*desc->copy其实就是调用_Block_object_assign函数)。
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
// 当 block 和 byref 要持有对象时,它们的 copy helper 函数会调用这个函数来完成 assignment,
// 参数 destAddr 其实是一个二级指针,指向真正的目标指针
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_use_RR() 中会被 Objc runtime 或者 CoreFoundation 设置 retain 函数,
// 其中,可能会与 runtime 建立联系,操作对象的引用计数什么的
_Block_retain_object(object);
// 使 dest 指向的目标指针指向 object
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
// 使 dest 指向的拷贝到堆上object
*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 指向的拷贝到堆上的byref
*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
*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
*dest = object;
break;
default:
break;
}
}
在这里switch case判断的类型有点多,但大体上分为以下几种:
// Runtime support functions used by compiler when generating copy/dispose helpers
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
普通对象的copy
普通的对象(不加任何修饰比如__weak, 或者__block)的copy会被Block持有,也就是*desc=object,另外也会去调用_Block_retain_object函数区进行增加引用计数:
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
// 默认什么都不干,但在 _Block_use_RR() 中会被 Objc runtime 或者 CoreFoundation 设置 retain 函数,
// 其中,可能会与 runtime 建立联系,操作对象的引用计数什么的
_Block_retain_object(object);
// 使 dest 指向的目标指针指向 object
*dest = object;
block的copy
如果捕获对象也是个block,那么就直接调用_Block_copy
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
// 使 dest 指向的拷贝到堆上object
*dest = _Block_copy(object);
__block修饰的对象
__block修饰对象跟普通对象不一样,在block里面它是一个结构体,它的copy实现是在函数_Block_byref_copy中实现的:
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 指向的拷贝到堆上的byref
*dest = _Block_byref_copy(object);
break;
接下来就来看下_Block_byref_copy函数:
// 1. 如果 byref 原来在堆上,就将其拷贝到堆上,拷贝的包括 Block_byref、Block_byref_2、Block_byref_3,
// 被 __weak 修饰的 byref 会被修改 isa 为 _NSConcreteWeakBlockVariable,
// 原来 byref 的 forwarding 也会指向堆上的 byref;
// 2. 如果 byref 已经在堆上,就只增加一个引用计数。
// 参数 dest是一个二级指针,指向了目标指针,最终,目标指针会指向堆上的 byref
static struct Block_byref *_Block_byref_copy(const void *arg) {
// arg 强转为 Block_byref * 类型
struct Block_byref *src = (struct Block_byref *)arg;
// 引用计数等于 0
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
// 为新的 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
// 新 byref 的 flags 中标记了它是在堆上,且引用计数为 2。
// 为什么是 2 呢?注释说的是 non-GC one for caller, one for stack
// one for caller 很好理解,那 one for stack 是为什么呢?
// 看下面的代码中有一行 src->forwarding = copy。src 的 forwarding 也指向了 copy,相当于引用了 copy
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
// 堆上 byref 的 forwarding 指向自己
copy->forwarding = copy; // patch heap copy to point to itself
// 原来栈上的 byref 的 forwarding 现在也指向堆上的 byref
src->forwarding = copy; // patch stack to point to heap copy
// 拷贝 size
copy->size = src->size;
// 如果 src 有 copy/dispose helper
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
// 取得 src 和 copy 的 Block_byref_2
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
// copy 的 copy/dispose helper 也与 src 保持一致
// 因为是函数指针,估计也不是在栈上,所以不用担心被销毁
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
// 如果 src 有扩展布局,也拷贝扩展布局
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);
// 没有将 layout 字符串拷贝到堆上,是因为它是 const 常量,不在栈上
copy3->layout = src3->layout;
}
// 调用 copy helper,因为 src 和 copy 的 copy helper 是一样的,所以用谁的都行,调用的都是同一个函数
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
// 如果 src 没有 copy/dispose helper
// 将 Block_byref 后面的数据都拷贝到 copy 中,一定包括 Block_byref_3
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
// src 已经在堆上,就只将引用计数加 1
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
// 引用计数加 1,最多不超过 BLOCK_REFCOUNT_MASK
// volatile的作用是:作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。简单地说就是防止编译器对代码进行优化
static int32_t latching_incr_int(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
// 如果 old_value 在第 1~15 位都已经变为 1 了,即引用计数已经满了,就返回 BLOCK_REFCOUNT_MASK
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return BLOCK_REFCOUNT_MASK;
}
// 比较 where 处的现在的值是否等于 old_value,如果等于,就将新值 oldValue + 2 放入 where
// 否则继续下一轮循环
// 这里加 2,是因为 flag 的第 0 位已经被占了,引用计数是第 1~15 位,所以加上 0b10,引用计数只是加 1
if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
return old_value+2;
}
}
}
在具体解释该函数之前,我们要先看一下Block_byref是什么?当我们用__block修饰要捕获的变量时,如下所示:
__block NSObject *objc = [NSObject new];
void (^block)(void) = ^ {
NSLog(@"%@",objc);
};
block();
其在编译后的代码如下所示:
struct __Block_byref_objc_0 {
void *__isa;
__Block_byref_objc_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *objc;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_objc_0 *objc; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_objc_0 *_objc, int flags=0) : objc(_objc->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_objc_0 *objc = __cself->objc; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9z_m9cbf92n62b13hr88w4n5ls80000gn_T_main_7419fc_mi_0,(objc->__forwarding->objc));
}
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, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);}
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};
__attribute__((__blocks__(byref))) __Block_byref_objc_0 objc = {
(void*)0,
(__Block_byref_objc_0 *)&objc,
33554432,
sizeof(__Block_byref_objc_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"))};
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_objc_0 *)&objc, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
可以看到编译后的内容也是非常多,在这里可以捋清一下脉络:
- 首先被
__block修饰的变量被包装成了__Block_byref_objc_0结构体, __forwarding参数的值就是obj对象包装后的地址即__Block_byref_objc_0__Block_byref_id_object_copy和__Block_byref_id_object_dispose都是一个函数地址,其最终指向的函数分别为_Block_object_assign和_Block_object_dispose- 包装
block的结构体__main_block_impl_0捕获的参数就是这里的__Block_byref_objc_0结构体,也就是包装后的objc对象。 传入的参数如下:
//这里是__Block_byref_objc_0结构体定义
struct __Block_byref_objc_0 {
void *__isa;
__Block_byref_objc_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *objc;
};
//这是传入参数 __Block_byref_id_object_copy_131
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
//这是传入参数 __Block_byref_id_object_dispose_131
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
//这里是传入参数
__attribute__((__blocks__(byref))) __Block_byref_objc_0 objc = {
(void*)0,
(__Block_byref_objc_0 *)&objc,
33554432,
sizeof(__Block_byref_objc_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"))};
有了这些知识,回到Block_byref函数中,该函数所做的事情就比较明朗了
- 栈上边原本的
byref中的forwarding指向自己(编译时通过(__Block_byref_objc_0 *)&objc赋的值) - 如果
byref原来在栈上,就将其拷贝到堆上, - 将堆上
byref的forwarding指向自己(也就是copy->forwarding = copy) - 将栈上
byref的forwarding现在也指向堆上的byref(也就是`src->forwarding = copy) - 如果
byref已经在堆上,就只增加一个引用计数。 其指向关系的修改大致如下图:
那么为什么这么设计呢?,可以看下如下代码:
__block FMPerson *objc = [FMPerson new];
void (^block)(void) = ^ {
objc.name = @"李四";
NSLog(@"%@--%@",objc,objc.name);
};
objc.name = @"张三";
NSLog(@"%@--%@",objc,objc.name);
block();
打印结果:
<FMPerson: 0x6000001cc1a0>--张三
<FMPerson: 0x6000001cc1a0>--李四
- 在这段代码中,
block外边的objc.name=张三的赋值,是对栈区的block赋值,在编译后其真实调用的是包装了捕获对象的byref中的__forwarding所指向的对象obj,如下图所示 - 而在
block_copy之后,栈区和堆区的__forwarding都指向拷贝到堆区的byref,然后通过objc.__forwarding->objc这种方式来取到所捕获的对象,这样就保证了数据的一致性
Block_copy总结
- 用
__block修饰的变量在编译过后会变成__Block_byref__XXX类型的结构体,在结构体内部有一个__forwarding的结构体指针,指向结构体本身。 block创建的时候是在栈上的,在将栈block拷⻉到堆上的时候,同时也会将block中捕获的对象拷⻉到堆上,然后就会将栈上block的__forwarding指针指向堆上block。 这样我们在block内部修改的时候虽然是修改堆上的对象的值,但是因为栈上的对象的__forwarding指针将堆的blcok做了关联,因此栈上blcok调用的时候objc.__forwarding就变成了堆block,objc.__forwarding.objc也就获取到了堆上的捕获的对象。也就达到同步修改的目的。
Block_dispose
看完了block的copy函数,那么dispose函数又是做什么的呢?其实也不难猜到,dispose函数是与copy函数成对存在,也就是用来释放copy的block,当然在编译后的文件中也能看到dispose函数的调用:
通过源码就能看到
_Block_object_dispose函数具体的实现:
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents
// 当 block 和 byref 要 dispose 对象时,它们的 dispose helper 会调用这个函数
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
// 如果是 byref
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
// 对 byref 对象做 release 操作
_Block_byref_release(object);
break;
// 如果是 block
case BLOCK_FIELD_IS_BLOCK:
// 对 block 做 release 操作
_Block_release(object);
break;
// 如果是对象
case BLOCK_FIELD_IS_OBJECT:
// 默认啥也不干,但在 _Block_use_RR() 中可能会被 Objc runtime 或者 CoreFoundation 设置一个 release 函数,里面可能会涉及到 runtime 的引用计数
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
普通对象的release
前文中也说到,普通对象的copy是被Block持有,然后调用_Block_retain_object处理引用计数问题,那在释放的时候调用外部的_Block_release_object函数进行引用计数处理就行。
block的release
如果捕获对象也是个block,就调用_Block_release函数,函数中的逻辑可看注释:
// 对 block 做 release 操作。
// block 在堆上,才需要 release,在全局区和栈区都不需要 release.
// 先将引用计数减 1,如果引用计数减到了 0,就将 block 销毁
void _Block_release(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
// 如果 block == nil
if (!aBlock) return;
// 如果 block 在全局区
if (aBlock->flags & BLOCK_IS_GLOBAL) return;
// block 不在堆上
if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
// 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 block 需要被销毁
if (latching_decr_int_should_deallocate(&aBlock->flags)) {
// 调用 block 的 dispose helper,dispose helper 方法中会做诸如销毁 byref 等操作
_Block_call_dispose_helper(aBlock);
// _Block_destructInstance 啥也不干,函数体是空的
_Block_destructInstance(aBlock);
free(aBlock);
}
}
__block修饰对象的release
这里就直接调用_Block_byref_release函数:
// 对 byref 对象做 release 操作,
// 堆上的 byref 需要 release,栈上的不需要 release,
// release 就是引用计数减 1,如果引用计数减到了 0,就将 byref 对象销毁
static void _Block_byref_release(const void *arg) {
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
// 取得真正指向的 byref,如果 byref 已经被堆拷贝,则取得是堆上的 byref,否则是栈上的,栈上的不需要 release,也没有引用计数
byref = byref->forwarding;
// byref 被拷贝到堆上,需要 release
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
// 取得引用计数
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
// 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 byref 需要被销毁
if (latching_decr_int_should_deallocate(&byref->flags)) {
// 如果 byref 有 dispose helper,就先调用它的 dispose helper
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
// dispose helper 藏在 Block_byref_2 里
(*byref2->byref_destroy)(byref);
}
free(byref);
}
}
}
另外要注意的是:
__block不可以用于修饰静态变量和全局变量。
循环引用
对于block来说,另一个要注意的店就是循环引用的问题,先看下代码:
self.block = ^{
self.name = @"fm";
};
当然这里会产生循环引用,产生循环引用的原因就是在block中使用了self或者成员变量,并最终形成self -> block -> self的引用关系,导致循环引用。
即便把self.name改成_name,block在捕获变量的时候也会把self对象捕获,最终形成你中有我我中有你的局面。
一般情况下我们可以使用__weak typeof(self) weakSelf = self;也就是弱引用的方式来解决循环引用,但在有GCD的情况下就会带来另外的问题:
-(void)method {
self.name = @"fm";
__weak typeof(self) weakSelf = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",weakSelf.name);
});
};
self.block();
}
在block中有dispatch_after等类似延迟操作时,有可能在延迟的这段时间内,weakSelf.name已经释放,导致此时weakSelf.name的值为null,为了避免类似的问题一般可以通过以下方式来解决该问题:
强弱共舞(weak strong dance)
通过__weak typeof(self) weakSelf = self;和__strong typeof(weakSelf) strongSelf = weakSelf;配合使用
-(void)test {
self.name = @"fm";
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%p",&strongSelf);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
self.block();
}
临时变量
将产生循环引用的self变成临时变量
-(void)test {
self.name = @"c++";
__block FMViewController *vc = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil;
});
};
self.block();
}
block参数
将self作为参数传递到block中
-(void)test {
self.name = @"fm";
self.block = ^(FMViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.block(self);
}
@weakify、@strongify
如果有使用过RAC的知道,在RAC中一般通过如下方式来解决循环引用
@weakify(self)
self.block = ^{
@strongify(self)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"%@",self);
});
};
在block外用@weakify(self)在block内用@strongify(self)就解决了循环引用问题,非常方便,那么他又是如何做的呢?
我们可以看下RAC的宏定义weakify和strongify
#define weakify(...) rac_keywordify metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
rac_keywordify的宏定义比较简单,如下代码,其实就是autoreleasepool {}的替换
#if DEBUG
#define rac_keywordify autoreleasepool {}
#else
#define rac_keywordify try {} @catch (...) {}
#endif
metamacro_foreach_cxt的参数就比较多了,如果对传入参数做替换后,就如下代码:
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
替换后:
autoreleasepool {}
metamacro_concat(
metamacro_foreach_cxt, metamacro_argcount(self))(rac_weakify_, , __weak, self)
而metamacro_concat的宏定义如下:
#define metamacro_concat(A, B) metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B
宏定义中的##为宏连接符,用来把传入的参数连接起来,如metamacro_concat(A, B) 就是把A、B连接起来变成AB
而metamacro_argcount宏定义如下
#define metamacro_argcount(...) \
metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
/**
* Returns the Nth variadic argument (starting from zero). At least
* N + 1 variadic arguments must be given. N must be between zero and twenty,
* inclusive.
*/
#define metamacro_at(N, ...) \
metamacro_concat(metamacro_at, N)(__VA_ARGS__)
通过注释,可以看出来metamacro_argcount(...)用来替换可变参数(...)的个数;举个例子
metamacro_argcount(@"obj")会被替换成1metamacro_argcount(@"obj",@“obj”)会被替换成2 所以metamacro_argcount(self)会被替换成1(只有一个参数) 因此weakify的宏定义就变成了:
autoreleasepool {}
metamacro_foreach_cxt ## 1 (rac_weakify_, , __weak, self)
也就是:
autoreleasepool {}
metamacro_foreach_cxt1 (rac_weakify_, , __weak, self)
而metamacro_foreach_cxt1的宏定义如下
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
在一一对应替换之后就变成了:
autoreleasepool {}
rac_weakify_(0,__weak,self)
而rac_weakify_的宏定义如下
#define rac_weakify_(INDEX, CONTEXT, VAR) \
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
因此@weakify最终就会被替换成下面的样子:
autoreleasepool {}
__weak __typeof__(self) self_weak_ = self
其实也就是__weak typeof(self) weakSelf = self;也可以通过Product > Perform Action > Preprocess看下编译后的结果:
而@strongify也是通过这样的宏定义,变成了:
__strong __typeof__(self_weak) self = self_weak,从而解决了循环引用的问题。