Block

作者: 玄裳 | 来源:发表于2016-11-29 11:50 被阅读0次

最近看了很多篇优秀的关于Block的文章, 本宝宝也来写一篇咯!如有雷同, 那正是我辛辛苦苦搬过来的. 我是一只搬砖酱, 搬砖本领强! 言归正传, 老司机要开车(≧▽≦)/啦啦啦 宝宝们自带小板凳儿快上车~~~

Block究竟是什么

我们通过clang(LLVM编译器)转换为我们可读源代码.

 clang -rewrite -objc 源代码文件名

下面我们转化Block语法:

 int main () {
       void (^block)(void) = ^{
       NSLog(@"block");
 };
     return 0;
}

转化的代码内容很多, 我们看看其中的一部分:

 struct __main_block_impl_0{ 
     void *isa; //类指针 指向 block 的 Class
     int Flags; //保存 Block 的引用计数,所处位置(堆上还是栈上或者数据区),是否有拷贝函数等信息
     int Reserved; //保留变量
     void *FuncPtr; //函数指针, 指向 Block 的实现
 
     struct __main_block_desc_0* Desc;//描述信息

     __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0){
         impl.isa = &_NSConcreteStrackBlock;//_NSConcreteStrackBlock 相当于class_t的结构体实例, 将Block指针赋值给Block的结构体成员变量isa
         impl.Flags = flags;
         impl.FuncPtr = fp;
         Desc = desc;
     }//结构体的构造函数
  };

我们可以看到__main_block_desc_0也是一个结构体, 我们看看里面有哪些东西:

 static struct __main_block_desc_0 {
     size_t reserved; //保留变量
     size_t Block_size; // Block 的大小
  };

其实Block内部是一个结构体.本身就是 OC 对象.

什么是带有局部变量的匿名函数?

顾名思义, 所谓匿名函数就是不带有名称的函数." 带有局部变量值"在 Block 中表现为"截取局部变量值". eg:

 int main ( )
 {
       int val = 10;
       const char *fmt = "val = %d\n";
       void (^block)(void) = ^ {
         printf(fmt,val);
       };
        val = 2;
        fmt = "These values were changed. val =%d\n ";
        block();
        return 0;
 } 

该代码执行结果:

 val = 10

可以看到执行结果并不是改写后的值 "These values were changed. val = 2 ", 当执行Block语法时, 字符串指针"val = %d\n" 被赋值到局部变量fmt中, int 值10被赋值到局部变量 val 中, 这些值被保存(即被截获). 即使修改了 Block 中使用的局部变量值也不会影响 Block 执行时局部变量的值. 这就是局部变量值的截获.

那么问题来了,如何修改 block 内部截获的局部变量值呢?
解决截获 Block 内部截取变量值的办法有两种, 一是 C 语言中的变量, 另一种是 __block 修饰符.

__Block修饰符

__Block修饰符类似于static、auto、register说明符, 用于指定将变量值设置到哪个储存域中, eg:

  • auto 作为局部变量存储在栈中

  • static 表示作为静态变量存储在数据区中

  • __block 指定 Block 中想变更值的局部变量

      __Block int val = 10;
      void (^block)(void) = ^{   
        val = 1; 
      };
    

我们来看看加上__Block修饰符后,源代码会发生哪些变化:

      struct __Block_byref_val_0{
          void *__isa;
          __Block_byref_val_0 *__forwarding;
          int __flags;
          int __size;
          int val; //原局部变量
     };
     
      struct __main_block_impl_0 {
          struct __block_impl impl;
          struct __main_block_desc_0* Desc;
          __Block_byref_val_0 *val;
     
          __main_Block_impl_0(void *fp, struct __main_Block_desc_0 *desc, __Block_byref_val_0 *val, int flags = 0) :val(val->_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_val_0 *val = _cself->val;
          (val -> __forwarding->val) = 1; //__Block_byref_val_0结构体实例的成员变量__forwarding指向该实例自身的指针
      }

      static void __main_block_copy_0 (struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src){
          _Block_Object_assign(&dst->val,src->val,BLOCK_FIELD_IS_BYREF);
      }
     
      static void __main_block_dispose_0(struct __main_block_impl_0 *src){
         _Block_Object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
      }

      static struct __main_block_desc_0{
          unsigned long reserved;
          unsigned long 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
       };
 
      int main(){
             __Block_byref_val_0 val ={
                  0,
                  &val,
                  0,
                  sizeof(__Block_byref_val_0),
                  10
             };//__block修饰的变量
    
             block = &_main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,&val,0x22000000);
             return 0;
      }      

只是在局部变量前附加了____block __修饰符,源代码量就剧增. 我们发现 ____block__ 变量 __val__ 变为了结构体实例. ____block 变量__也变成了结构体类型的局部变量 ____Block_byref_val_0__ , 变成了栈上生成的 ____Block_byref_val_0__ 结构体实例.

Block存储域

block与__block变量的实质.png Block源代码中的类.png

变量作用域结束时, 栈上的 ____block 变量和 Block __也被废弃, 但是将配置在栈上的 Block 复制一份到堆上, 即使变量作用域结束, 堆上的 __block 变量和 Block 不受影响.

____block 变量用结构体成员变量 ____forwarding 可以实现:
无论 ____block 变量配置在栈上还是堆上都可以正确访问 ____block 变量. ( copy 到堆上后栈上的 ______forwarding 指向 copy 指到堆上的那份~)如图:

栈上的__forwarding指针.png
栈上的__forwarding复制后指针变化.png

PS: ARC状态下, 不管 Block 配置在何处, 用 copy 方法复制都不会引起任何问题, 在不确定时调用 copy 函数就好啦~

__block变量存储域

使用 ____block 变量__的 Block 从栈复制到堆上时, __block 变量也会受到影响.

Block从栈复制到堆时对__block变量产生的影响.png

多个Block中使用 ____block变量__ 时, 最先的 Block 从栈复制一份到堆上, 当然______block变量也会复制一份到堆上并被 Block 持有. 而剩下复制一份到堆上的 Block, 不仅持有______block 变量并且增加 __block 变量的引用计数.

block相关面试题

1.可变数组需要用 ______block 修饰符吗?为什么?
不需要,因为数组的指针并没有变(往数组里面添加对象,指针是没变的,只是数组里对象指针指向的内容变了
2.Block、Delegate、通知的区别是什么?
使用场景: Block 用于动画,数据请求回调,枚举回调,多线程GCD等.. Delegate 用于公共接口,方法较多时用 Delegate 进行解耦等.. 通知适用于多个控制器间需要知道一个事件,相隔多层的控制器之间跳转等..
* Delegate 和通知一般都是一对一的通信,通知一般是一对多之间的通信;
* Delegate 需要定义协议方法,代理对象实现协议方法,并要建立代理关系才能实现通信;
* Block 代码写在 Block 块中更加简洁清晰,通信事件较多情况,建议使用 Delegate;
* Delegate 运行成本低, 效率比 通知高, Block 运行成本高。Block 出栈需要将使用的数据从栈内存拷贝到堆内存,对象的情况引用计数增加,使用完或者 Block 置 nil 后才消除;Delegate 只是保存了一个对象指针,直接回调,没有额外消耗。相对C的函数指针,只多做了一个查表动作;
* Delegate 注重信息传输的过程,Block注重信息传输的结果.比如发起一个网络请求,想知道请求是否已经开始、是否收到了数据、数据是否已经接受完成、数据接收失败时建议使用 Delegate ;对于一个事件,只想知道成功或者失败,而不需要知道其他信息则建议使用Block
* 一个 viewController 中有多个弹窗事件,Delegate 需要对每个事件进行判断识别来源, Block 可以在创建事件时区分。
* 代理执行协议方法时要使用 respondsToSelector 检查其代理是否符合协议(检查对象能否响应指定的消息),以避免代理在回调时因为没有实现方法而造成程序崩溃, Block 使用时要注意防止循环引用,通知一旦接收消息的对象多了,可读性差,不易维护.创建观察者一定要dealloc中移除.

3.Block用assign和retain修饰符会出现什么问题?
会发生Crash
为什么会Crash?
因为 MRC 下 Block 生成是在栈上的,只有 copy 函数能把栈上的 block 复制一份到堆上,这样不会导致 block 出了作用域就会被释放的问题.堆上的 Block 可以由我们自己手动管理, retain 对栈上的 Block 是没用的
4.MRC下, ______block 修饰变量为什么可以打破循环?
因为MRC状态下, __block 变量是在 Block 中引入一个新的结构体成员变量指向这个__block变量,这样 Block 捕获的id变量不会被retain. Block 中使用的变量没有 __block 修饰的对象或者局部变量,就会被retain.造成循环引用.
5.____block__ 修饰符什么时候使用?
在Block里面修改局部变量的值都要用__block修饰符
6.什么时候需要在 Block 中使用 weakSelf/strongSelf ?
在 Block 内如果需要访问 self 的方法、变量,防止循环引用使用 weakSelf。如果在 Block 内需要多次访问 self ,保证不被释放则需要使用 strongSelf.
7.使用 Block 如何避免循环引用 ?
根据不同情况可以使用 __block 变量、__weak 修饰符或 __unsafe_unretained 修饰符来避免循环引用。

感谢小笨狼哥哥和师父以及我们TeamiOS成员的悉心指导,上面代码都是手打的,如有错误,请大家及时指出.非常感谢~

相关文章

  • iOS开发之Block原理探究

    Block概述 Block本质 Block调用 Block分类 Block循环引用 Block原理探究 Block...

  • block的使用

    定义block 返回类型 (^block名称)(参数) = ^(){block内容}; 调用block block...

  • Block 02 - __block

    Block 02 - __block __block 的作用 __block 可以解决 Block 内部无法修改 ...

  • iOS面试之Block大全

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS面试之Block模块

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS Block

    Block的分类 Block有三种类型:全局Block,堆区Block,栈区Block 全局Block 当Bloc...

  • iOS block 为什么官方文档建议用 copy 修饰

    一、block 的三种类型block 三种类型:全局 block,堆 block、栈 block。全局 block...

  • iOS开发block是用copy修饰还是strong

    Block分为全局Block、堆Block和栈Block1、在定义block没有引用外部变量的时候,block为全...

  • block 初探

    全局block, 栈block, 堆block

  • Block

    一、Block本质 二、 BlocK截获变量 三、__block 修饰变量 四、Block内存管理 五、Block...

网友评论

      本文标题:Block

      本文链接:https://www.haomeiwen.com/subject/trpapttx.html