一、block 分类
block 常规使用下 分为 全局 堆 栈 但是还有系统级别的3种 所以有六种
- 全局blok
void(^block)(void) = ^{
};
block();
NSLog(@"---block---%@",block);// <__NSGlobalBlock__: 0x1000c8190>
- 堆区
int a = 10;
void(^block)(void) = ^{
NSLog(@"----blocka--%d",a);
};
block();
NSLog(@"---block---%@",block);// <__NSMallocBlock__: 0x600001c3cf90>
- 栈区
NSLog(@"---staticblock---%@",^{
NSLog(@"----blocka--%d",a);
});
// ---staticblock---<__NSStackBlock__: 0x7ffee2a90f70>
二、block 循环引用
typedef void(^myBlock)(void);
@property(nonatomic, copy)NSString *name;
@property(nonatomic, copy)myBlock block;
self.name = @"123123";
self.block = ^{
NSLog(@"----blocka--%@",self.name);
};
self.block();
上述代码会产生循环引用 原因 self 持有block block又持有self 这两个在释放的时候回相互等待 所以造成内存泄漏 所以要用__weak 打破引用链
self.name = @"123123";
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"----blocka--%@",weakSelf.name);
};
self.block();
解释 __weak不会引起引用计数的加1 所以self会释放 打破引用链 更完美的写法是
self.name = @"123123";
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"----blocka--%@",strongSelf.name);
};
self.block();
防止在self释放之后再访问name
三、block 底层原理
- 本质是什么 通过clang 编译如下简单代码
int main(){
void(^block)(void) = ^{
};
block();
return 0;
}
结果
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}
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)};
int main(){
void(*block)(void) = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
(((__block_impl *)block)->FuncPtr)((__block_impl *)block);
从上边代码可以看出 block本质上是一个结构体 构造函数中保存了 isa flags funcPtr desc 将函数的调用保存在了 funcPtr中
- 捕获外界变量 再次clang
int main(){
int a = 10;
void(^block)(void) = ^{
printf("------%d",a)
};
block();
return 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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy 此处的a已经不是外部的a了
printf("------%d",a);
}
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a)); 将a作为参数进行传递
从上边代码看 block 会重新声明一个a 将原来外部的a的值赋值给 block结构体的a
- __block 的实现
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref
int main(){
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
上边代码 多了几个关键点 1. 多了 (__Block_byref_a_0 *)&a
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
__block 修饰了以后 会多出来一个结构体 结构体中保存了外部的值和指针然后传递给block的结构体 所以此时block内部可以修改外部的变量
四、block 堆栈变化
开始读寄存器
register read x0
x0 = 0x000000016d457a58
(lldb) po 0x000000016d457a58
<__NSStackBlock__: 0x16d457a58>
signature: "v8@?0"
invoke : 0x1029accc4 (/var/containers/Bundle/Application/3D601EBA-87C6-473A-ABCD-BB1B8E8F40CC/KVOTest.app/KVOTest`__22-[FirstVC viewDidLoad]_block_invoke)
0x102e74c48 <+396>: bl 0x102e75ec0 ; symbol stub for: objc_retainBlock 之后再下断点 再读寄存
register read x0
x0 = 0x0000000282a65140
(lldb) po 0x0000000282a65140
<__NSMallocBlock__: 0x282a65140>
signature: "v8@?0"
invoke : 0x1029accc4 (/var/containers/Bundle/Application/3D601EBA-87C6-473A-ABCD-BB1B8E8F40CC/KVOTest.app/KVOTest`__22-[FirstVC viewDidLoad]_block_invoke)
此时已经变成 堆上block
五、block 源码解读
- _Block_copy 源码
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 =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
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
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;
}
}
上述代码 如果是全局block就直接返回 如果是栈上block就执行一步copy。malloc开辟内存
- 如何copy的?
__block NSString *name = [NSString stringWithFormat:@"xiaobing"];
void(^block)(void) = ^{
name = @"hehe";
NSLog(@"--ccc-----%@",name);
};
block();
执行 xcrun -sdk iphonesimulator clang -rewrite-objc main.m
看如下关键代码
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->name, (void*)src->name, 8/*BLOCK_FIELD_IS_BYREF*/);}
来看_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: // 是否是个对象
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK: // 是否是个block
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK: // 是否是__block 修饰的
case BLOCK_FIELD_IS_BYREF:
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
*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:
*dest = object;
break;
default:
break;
}
}
直接来看_Block_byref_copy的源码 被block修饰过的 只看关键代码
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
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;
(*src2->byref_keep)(copy, src);
}
return src->forwarding;
}
上述代码是将 copy出来的和原来的指向同一块内存 所以__block修饰的变量可以在block内部进行修改
block 的拷贝分为三个过程 1. block从栈到堆。2. 通过block_ref结构体对变量进行copy
3. 对block外的变量的值进行copy
- 析构过程。
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
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
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
}
}
这里着重看下 BLOCK_FIELD_IS_BYREF 因为这种情况下block 内部生成了一份copy 内存要自己管理 源码如下
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->forwarding;
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);
}
free(byref);
}
}
}
总结 block的使用早都会了 但是对它底层的原理所知甚少 今天终于搞清楚block什么时候从栈到堆 什么情况下是全局block ,并且block 的copy细节 也清楚了。