BlackCat's

Block 个人理解

一、block 是什么?

写了3年代码,说起 block听得最多的说法说的是 block 是一个匿名函数,那什么是 block?(自己见解,如果错误欢迎拍砖)

1、使用block 注意的地方?

其实我们写代码的时候,用到block 无非就是两点比较要注意。

1、会用__block 去修饰外部变量,原因吗多数也会说出来 block 不能捕获外部变量。
2、block 外部要用 weak 来修饰,内部用 strong 来修饰。这原因的也挺好说,无非是 block 在捕获外部变量的时候会保持一个强引用,当 block 内部捕获 self 的时候,由于对象对 block 进行 copy(也就是互相持有),就形成了循环引用。然后用了weakSelf后,在 block 里要是多次调用就可能被释放了,所以需要 block 中将 weakSelf “强化“。

然而少年,你以为这样就算理解了吗?太天真了。让我们继续往下走

block 的结构

* _ block impl: 这是一个结构体,也是C面向对象的体现,可以理解为block的基类;
struct __block_impl { 
void *isa;int Flags;
int Reserved;
void *FuncPtr;
};
* _ main block_ impl_0: 可以理解为block变量;
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;
  }
};
* _ main block func 0: 可以理解为匿名函数;
static void __main_block_func_0(struct
__main_block_impl_0 *__cself)
 {
 NSLog((NSString *)&__NSConstantStringImpl__var_folders_gc_5fkhcz0n6px48vzc744hmp6c0000gn_T_main_eef954_mi_0);
 }
* _ main block desc 0:block的描述, Block_size;
{
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
 void (*myblock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock);
}
return 0;
}

一串代码看着是不是很烦?我就谈谈我的理解吧

1、其实呢_main block impl 0 就是该 block 的实现,一个 block 实际是一个对象,它主要由一个 isa 和 一个 impl 和 一个 descriptor 组成。

在回头去看最开始的

1、 block 修饰的外部变量引用,是将栈上用block修饰的自动变量封装成一个结构体,让其在堆上创建。

__block int i = 1;

其实本质上会是 在__main_block_impl_0 转变成:

__Block_byref_i_0 *i;

i变量不再是int类型了,i变成了一个指向__Block_byref_i_0结构体的指针

在看__Block_byref _i _0

struct __Block_byref_i_0 {
    void *__isa;
    __Block_byref_i_0 *__forwarding;
    int __flags;
    int __size;
    int count;
};

其实说白了,也就是复制了外部变量的内存地址。

2、block 循环引用的原因呢?说穿了也即是互相持有。
一个对象A有Block类型的属性,从而持有这个Block,如果Block的代码块中使用到这个对象A,或者仅仅是用用到A对象的属性,会使Block也持有A对象,导致两者互相持有,不能在作用域结束后正常释放。

解决原理:对象A照常持有Block,但Block不能强引用持有对象A以打破循环。

__weak typeof(self)

一些面试的时候可能会问的

1、为什么 block 使用 copy 修饰的?

因为 block 其实是在栈里的,ARC 自动 copy 到堆上,执行完了就会释放。所以外部调用需要用 copy,strong会造成野指针。