揭开block的面纱-读书笔记

本文深入解析Block的内部机制,包括Block的基本概念、编译过程、截获自动变量值的方法、__block变量的使用、栈上Block复制到堆的条件、__block变量存储域以及Block循环引用的产生和避免。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一,概述

Block是“带有自动变量值得匿名函数”,Block看上去好像很特别,但它实际上是作为极普通的C语言源代码来处理的。通过clang -rewrite-objc 资源文件 命令,得到 资源文件.cpp文件,分析可以知道block内部的实现原理。

一,编译举例

比如如下代码:

int main()
{
    static int static_val = 3;

    int (^blk)() = ^{
        (*static_val) = 30;
        return 0;
    }

    int j = blk();
}

经过编译转换后为:

int main()
{

    static int static_val = 3;

    int (*blk)() = ((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));

    int j = ((int (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    return 0;
}

从中我们可以看出,block 就是一个结构体__main_block_impl_0,这个结构体中包含了一个isa指针,方法的地址等,如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

其中__block_impl为:

struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
};

方法实现__main_block_func_0为:

static int __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int *static_val = __cself->static_val; // bound by copy

      global_val = 10;
      static_global_val = 20;
      (*static_val) = 30;

      return 0;
}

以及__main_block_desc_0:

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)};

二,截获自动变量值,及改写被截获自动变量值的方法

1. 第一种,C语言中有一种变量,允许Block改写值

  • 静态变量
  • 静态全局变量
  • 全局变量

例子:

        int global_val = 1;
        static int static_global_val = 2;

        int main()
        {
            static int static_val =  3;

            void (^blk)(void) = ^{
                global_val *= 1;
                static_global_val *= 2;
                static_val *= 3;
            }
            return 0;
        }

        转换C语言后:

        struct __main_block_impl_0{
            struct __block_impl  impl;
            struct __main_block_desc_0* Desc;
            int *static_val;

            // 构造函数
            __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {

                impl.isa = &_NSConcreteStackBlock;
                impl.Flags = flags;
                impl.FuncPtr = fp;
                Desc = desc;

            }
        }
        // 方法的实现
        static int __main_block_func_0(struct __main_block_impl_0 *__cself) {
        int *static_val = __cself->static_val; // bound by copy

            global_val = 10;
            static_global_val = 20;
            (*static_val) = 30;

            return 0;
        }

总结,静态全局变量全局变量,是直接拿过来用,静态变量是在block结构体中拿到其地址(指针)来改变其值。

2. 第二种方法是使用__block说明符,更准确的表述方式为“__block存储域类说明符”
C语言有以下存储域说明符

1. typedef
2. extern
3. static 存到数据区中
4. auto  存到栈
5. register

三,__blcok变量编译

使用__block的代码如下:

int main()
{
   typedef void (^blk_t)();

   int __block var = 1;
   blk_t blk = ^{
       var = 2;
   };

   return 0;
}

经过编译后:

int main()
{

    typedef void (*blk_t)();

    blk_t blk;
    __Block_byref_var_0 var = {(void*)0,(__Block_byref_var_0 *)&var, 0, sizeof(__Block_byref_var_0), 1};
    blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_var_0 *)&var, 570425344));

    return 0;
}

可以看出__block变量其实被转换为__Block_byref_var_0结构体:

其中__forwarding的作用:__block变量用结构体成员变量__forwarding可以实现__block变量配置在栈上还是堆上时都能够正确访问__block变量。

struct __Block_byref_var_0 {
  void *__isa;
__Block_byref_var_0 *__forwarding;
 int __flags;
 int __size;
 int var;
};

方法的实现为:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_var_0 *var = __cself->var; // bound by ref

    (var->__forwarding->var) = 2;
}

另外__block变量需要控制生命周期,又出现了下面两个由栈copy到堆的copy方法,和销毁的dispose方法

static void __main_block_copy_0(
struct __main_block_impl_0*dst, 
struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->var, (void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(
struct __main_block_impl_0*src
) {
_Block_object_dispose((void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);
}

四,什么时候栈上的Block会复制到堆

(将block从栈上复制到堆上相当消耗CPU)

1. 调用Block的copy实例方法
2. Block作为函数返回值返回时
3. 将Blcok赋值给附有__strong修饰符id类型的类或者Block类型成员变量时
4. 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中传递Block时

五,__block变量存储域

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

总结如下:

1. __block变量的存储域 在栈时,从栈复制到堆并被Blcok持有。
2. __block变量的存储域 在堆时,被Blcok持有。
3. 当多个Block中使用__block变量时,当任何一个Block从栈复制到堆时,__block变量也会一并从栈复制到堆并被该Block所持有。当剩下的Block从栈复制到堆时,被复制的Block持有__block变量,并增加__block变量的引用计数。

__block修饰符可以和__strong,__weak,__unsafe_unretained修饰符共用,但是和__autoreleasing修饰符共用会产生编译错误。

六,Block循环引用

1. 引发循环引用的例子:

  • object对象的强引用MyBlock类型成员blk_。在init方法中有MyBlock类型的myblock,使用附有__strong修饰符的id类型变量self,并且myblock赋值给object的成员变量blk_,此时myblock(或者blk_)由栈复制到堆,并持有所使用的self。self持有myblock,myblock持有self,发生了循环引用。
  • 如下代码中block内没有使用self也同样截获了self,引起了循环引用。

        typedef void (^blk_t)();
        @interface MyObject : NSObjec{
            blk_t blk_
            id obj_
        }
        @end
        @implementation MyObject
    
        -(id)init{
            self = [super init];
            blk_ = ^{
                NSLog(@“obj_ = %@”,obj_); // 其中obj_相当于self->obj_,也会截获sel
            }
            return self;
        }
    
        @end
    

2. 怎样避免循环引用

  • 使用__weak或__unsafe_unretain修饰符
  • 使用__block修饰符,例:

        - (id)init
        {
            self = [super init];
    
            __block id temp = self;
    
            blk_ = ^{
                temp = nil;
            }
    
        }
    
        - (void)execBlock
        {
            blk_();
        }
    

    如果不调用execBlock实例方法,便会导致循环引用。

3. 使用__block变量和__weak及__unsafe_unretained修饰符避免循环引用的方法比较

  • 使用__block变量优点如下:

    1. 通过__block变量可控制对象的持有期间
    2. 在不能使用__weak修饰符的环境中不使用__unsafe_unretained修饰符即可(不必担心悬垂指针)
      使用Block时可动态地决定是否将nil或其他对象赋值在__block变量中
  • 使用__block变量的缺点如下:

    1. 为避免循环引用必须执行Block.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值