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成员的悉心指导,上面代码都是手打的,如有错误,请大家及时指出.非常感谢~

    相关文章

      网友评论

          本文标题:Block

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