深入了解 ios Block 的内部结构

首先复习一下之前写过的关于 block 的内容

1>>> block 的定义及格式

就拿无返回值 有参数举个例子算了

typedef void(^MyBlock)(NSString * str)

2>>> block 的几种类型(三种)

_NSConcreteGlobalBlock:全局的静态 block,类似函数。如果block里不获取任何外部变量。或者的变量是全局作用域时,如成员变量、属性等; 这个时候就是Global类型

_NSConcreteStackBlock:保存在栈中的 block,栈都是由系统管理内存,当函数返回时会被销毁。__block类型的变量也同样被销毁。为了不被销毁,block会将block和__block变量从栈拷贝到堆。

_NSConcreteMallocBlock:保存在堆中的 block,堆内存可以由开发人员来控制。当引用计数为 0 时会被销毁。

所以声明一个 block属性时记得用 copy 来修饰

3>>> block 的作用

其实就是匿名函数, 闭包, 或者 js那种回调函数, 可以代替代理模式那种复杂的步骤, 一个最常用的用法, 比如网络请求获取内容, 先定义一个 block 在你请求完成之后, 调用 block, 那么当你调用请求方法的时候, 就可以在 block 里做解析收到网络请求内容的操作了, 就像 callback, 或者用来反向传值也都是比较方便的, 或者用来捕获当前作用域的变量

4>>> block 需要注意的问题

当在 block 中用到局部变量时, 全局变量可以修改, 局部变量不能修改他的值, 如果一定要修改, 请用__  block 修饰, 问起原因, 后面会做详细解析, __block 修饰的 block 变量其实就是一个结构体

另一个问题就是循环引用, 例如当前类持有一个 block 的属性, 然后在 block 的实现里又引用了当前类, 就会导致循环引用, 当前对象不能释放 ,内存泄漏, 解决办法就 weak strong dance, 在 block 外部先用 weak 修饰 在 block 内部再用 strong 修饰

----------------------------------******************-----------------------------------

新建一个 testBlock.m 文件 , 运行 clang -rewrite-objc testBlock.m 可以翻译成. cpp的 C++源码

// 声明一个 blockName的 block  输出打印 block 函数 然后执行这个 block

翻译后的

翻译成源码后有很多文件 主要的文件就这四个

 

__main_block_func_0    //这是block要执行的函数, 也就是 block 块内的内容

 

_main_block_desc_0 // 这是block的描述信息的结构体 对于我们来说没什么作用

 

_main_block_impl_0 // 这就是 block 实现部分的入口 这里可以看出一些赋值 和 block 的类型

 

_block_impl  这个就是主角了 这是 block 的真正结构-结构体  含有 isa 指针, 这也是为什么很多人说 block 可以看做对象的原因 , 因此 block 也可以赋值为 nil

FuncPtr 可以在 int main 中看到 blockName -> FuncPtr 指向了函数的实现

这个是没有在 block 内部获取局部变量的  下面看看 block 内部是如何获取局部变量的

--------------------------------------**************--------------------------------

我们在上面的 block 外面声明一个 int num = 10 的局部变量  然后看他的 cpp 实现 发现有什么不同, 多了一个 num变量, 这里的__ cself 就相当于 OC 的 self, 在 block 定义的时候就已经把这个 num的变量值拷贝过来了 , 即相当于拷贝了 num 的值进 block 内部, 相当于一个快照, 与外部的 num 变量没有关系了, 所以没法修改 num 的值

 

再看看__block 修饰变量的时候

这就发生了巨大的变化 多了一个__ Block_byref_num_0 的变量, 这个是什么呢? 上面就可以看到这个一个结构体, 里面有个 num的外部变量, 还有 forwarding的指向堆上的指针

也就是说 加上__ block int num 之后变量就变成了 __Block_byref_num_0的指针, 也即是 num通过这个指针传递给了 block, 而不是 block 只捕获了他的值, 所以block 内部改变变量的值就变成了 在block要执行的函数 __main_block_func_0中,我们通过__Block_byref_num_0的__forwarding指针来修改的 外部变量,即:(num->__forwarding->num) = 10;

就是修改 forwarding 指向的内存的值 

 

//再啰嗦一点, block 不copy 的话, 是声明在栈上的 像 int a = 10一样, copy 之后 block 在堆上 , 同时__ block 修饰的 block 变量也会 copy 一份指针变量到堆上, 而没有使用__ block 修饰的依然在栈上

posted @ 2018-03-09 15:14  ChrisZhou6605  阅读(338)  评论(0编辑  收藏  举报