Block的原理学习

Block的定义

  1. Blocks是C语言的扩充功能。可以用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。

  2. Block的语法声明:返回值类型 (^变量名)(参数列表) = ^ 返回值类型 (参数列表) 表达式

    • 代码表示:void (^block)(void) = ^void (void){};

    • 或者使用typedef来定义一个block:typedef void (^block)(void);

分类

在iOS中,主要有三种Block类型,NSGlobalBlockNSStackBlockNSMallocBlock。这三种block类型主要是以block所在的储存空间作为区分依据。

  1. 全局Block:NSGlobalBlock

    • 位于内存全局区,.data区域

    • 在Block内部,没有访问外部的变量

    • (__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject)

  2. 栈Block:NSStackBlock

    • 位于内存的栈存储区,

    • 在Block内部,访问外部变量,但是只读操作,不能进行"写"操作

    • (__NSStackBlock__ : __NSStackBlock : NSBlock : NSObject)

  3. 堆Block:NSMallocBlock

    • 位于内存的堆存储区

    • 堆Block是栈Block的复制,因此在Block内部会访问外部变量,也是只读操作,不能进行"写"操作

    • (__NSMallocBlock__ : __NSMallocBlock : NSBlock : NSObject)

  4. 三种类型存储示意图:

block类型.png
  1. Block的分类简单用下图表示


    Block分类.png

为了方便后续的分析和展示,需要把代码编译成.cpp文件。编译指令如下

  • 模拟器:xcrun -sdk iphonesimulator clang -rewrite-objc xxx文件名

  • 真机: xcrun -sdk iphoneos clang -rewrite-objc xxx文件名

全局区Block(NSGlobalBlock)
  1. 没有外部变量引用的block就是全局区Block,如下代码:

      //全局block NSGlobalBlock
    -(void)globalBlock{
       void(^globalBlock)(void) = ^{
          NSLog(@">>>>>globalBlock<<<<<");
       };
       NSLog(@"当前的block类型为>>>>>%@",globalBlock);
      globalBlock();
    }
    ------------------------------------------------------
    
    //打印结果为:
    2022-04-20 14:38:22.668397+0800 suanfaProject[5151:219121] 当前的block类型为>>>>><__NSGlobalBlock__: 0x102b04cc0>
    2022-04-20 14:38:22.668573+0800 suanfaProject[5151:219121] >>>>>globalBlock<<<<<
    
  2. 编译后的.cpp文件,很复杂,直截取需要的内容:

    struct __block_impl {
      //isa指针,指向具体的Block的类,当前是NSGlobalBlock类型的block,指向为__NSGlobalBlock__
      void *isa;
      //表示一些block的附加信息,涉及到引用指数和销毁的判断,会使用到该值
      int Flags;
      //保留变量
      int Reserved;
      // 函数指针,指向具体的 block 实现的函数调用地址
      //block将需要执行的代码创建一个函数,impl内部的FuncPtr指向这个函数的地址,通过地址调用这个函数,就可以执行block里面的代码
      //函数名称类似于 __类名__调用block的方法名_block_func_标记数 :__BlockClang__globalBlock_block_func_0
      void *FuncPtr;
    };
    
    // @implementation BlockClang
    ///这个结构体就是block的实际内容。
    struct __BlockClang__globalBlock_block_impl_0 {
      struct __block_impl impl;
      //block的描述信息,
      struct __BlockClang__globalBlock_block_desc_0* Desc;
      //结构体的同名构造函数
      //fp:是block是根据执行的代码而创建的函数的函数指针
      //desc:block的描述信息结构体
      __BlockClang__globalBlock_block_impl_0(void *fp, struct __BlockClang__globalBlock_block_desc_0 *desc, int flags=0)   {
          //在编译阶段所有类型的block的isa都指向_NSConcreteStackBlock,也就是说block类型是在运行时确定的。
          impl.isa = &_NSConcreteStackBlock;
          impl.Flags = flags;
          impl.FuncPtr = fp;
          Desc = desc;
      }
    };
    
    //block将需要执行的代码创建一个函数
    static void __BlockClang__globalBlock_block_func_0(struct __BlockClang__globalBlock_block_impl_0 *__cself) {
        //里面是一句打印代码
        NSLog((NSString*)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_8949bb_mi_0);
      }
    
    //block的描述信息结构体,并给出一个默认值
    static struct __BlockClang__globalBlock_block_desc_0 {
        size_t reserved;
        size_t Block_size;
     } __BlockClang__globalBlock_block_desc_0_DATA = { 0, sizeof(struct __BlockClang__globalBlock_block_impl_0)};
    
    //-(void)globalBlock的方法,编译出来的文件
    static void _I_BlockClang_globalBlock(BlockClang * self, SEL _cmd) {
      //void(*globalBlock)(void) = &__BlockClang__globalBlock_block_impl_0(__BlockClang__globalBlock_block_func_0,   &__BlockClang__globalBlock_block_desc_0_DATA));
      void(*globalBlock)(void) = ((void (*)())&__BlockClang__globalBlock_block_impl_0((void       *)__BlockClang__globalBlock_block_func_0, &__BlockClang__globalBlock_block_desc_0_DATA));
    
      //打印函数
      NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_8949bb_mi_1,globalBlock);
      //调用globalBlock
      //去掉类型强转后的代码:globalBlock->FuncPtr(globalBlock); //即调用globalBlock结构体中FuncPtr参数,传值为block本身
      ((void (*)(__block_impl *))((__block_impl *)globalBlock)->FuncPtr)((__block_impl *)globalBlock);
    }
    // @end
    
  3. 总结以上述代码

    • Block的输出为__NSGlobalBlock__,这也就说明了,是一种全局Block

    • Block内部没有访问外部的auto变量

栈Block(NSStackBlock)
  1. 非强引用Block或者非copy的Block,类型就是栈Block。ARC下需要使用__weak修饰就是stackBlock,代码实例如下

     //栈block NSStackBlock
    -(void)stackBlock{
        int age = 10;
        void(^ stackBlock)(void) = ^{
            NSLog(@">>>>>stackBlock<<<<<, age : %d",age);
        };
        NSLog(@"当前的block类型为>>>>>%@",stackBlock);
        stackBlock();
    }
    ------------------------------------------------------
    //打印结果为:
    2022-04-20 15:49:59.063036+0800 suanfaProject[5886:260026] 当前的block类型为>>>>><__NSStackBlock__: 0x16b5c5938>
    2022-04-20 15:49:59.063267+0800 suanfaProject[5886:260026] >>>>>NSStackBlock<<<<<, age : 10
    
  2. 编译后的.cpp文件,如下

     struct __block_impl {
        //isa指针,指向具体的Block的类,当前是NSGlobalBlock类型的block,指向为__NSGlobalBlock__
        void *isa;
        //表示一些block的附加信息,涉及到引用指数和销毁的判断,会使用到该值
        int Flags;
         //保留变量
        int Reserved;
        // 函数指针,指向具体的 block 实现的函数调用地址
        //block将需要执行的代码创建一个函数,impl内部的FuncPtr指向这个函数的地址,通过地址调用这个函数,就可以执行block里面的代码
        //函数名称类似于 __类名__调用block的方法名_block_func_标记数 :__BlockClang__globalBlock_block_func_0
        void *FuncPtr;
      };
    
      // @implementation BlockClang
      //block的结构体
      struct __BlockClang__stackBlock_block_impl_0 {
        struct __block_impl impl;
        struct __BlockClang__stackBlock_block_desc_0* Desc;
    
        //这里多了一个age的参数,是和全局Block的文件不同的地方
        int age;
        //结构体的同名构造函数
        //fp:是block是根据执行的代码而创建的函数的函数指针
        //desc:block的描述信息结构体
        //多个一个age的赋值,看的出来这里是一个值的复制,并不是指针
        __BlockClang__stackBlock_block_impl_0(void *fp, struct __BlockClang__stackBlock_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
           impl.isa = &_NSConcreteStackBlock;
          impl.Flags = flags;
          impl.FuncPtr = fp;
          Desc = desc;
        }
      };
    
      //block将需要执行的代码创建一个函数
      static void __BlockClang__stackBlock_block_func_0(struct __BlockClang__stackBlock_block_impl_0 *__cself) {
          int age = __cself->age; // bound by copy
          //打印代码
          NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_7d0b37_mi_0,age);
      }
    
    
      //block的描述信息结构体,并给出一个默认值
      static struct __BlockClang__stackBlock_block_desc_0 {
          size_t reserved;
          size_t Block_size;
      } __BlockClang__stackBlock_block_desc_0_DATA = { 0, sizeof(struct __BlockClang__stackBlock_block_impl_0)};
    
      //-(void)stackBlock的方法,编译出来的文件
      static void _I_BlockClang_stackBlock(BlockClang * self, SEL _cmd) {
          //外部变量
          int age = 10;
          // void(* stackBlock)(void) = &__BlockClang__stackBlock_block_impl_0(__BlockClang__stackBlock_block_func_0, &__BlockClang__stackBlock_block_desc_0_DATA, age));
          void(* stackBlock)(void) = ((void (*)())&__BlockClang__stackBlock_block_impl_0((void *)__BlockClang__stackBlock_block_func_0, &__BlockClang__stackBlock_block_desc_0_DATA, age));
          //打印函数
          NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_7d0b37_mi_1,stackBlock);
          //调用globalBlock
          //去掉类型强转后的代码:stackBlock->FuncPtr(stackBlock); //即调用globalBlock结构体中FuncPtr参数,传值为block本身
          ((void (*)(__block_impl *))((__block_impl *)stackBlock)->FuncPtr)((__block_impl *)stackBlock);
      }
      // @end
    
  1. 给Block内引用的外部变量age,进行复制操作,观察现象

    栈Block赋值操作.png

    由图上可知,在block内部使用block外部定义的局部变量时,在block内部是只读的,不能对他进行修改,如果想要修改,变量前要有__block修饰,或者使用static修饰。

  2. 总结以上述代码

    • Block的输出为__NSStackBlock__,这也就说明了,是一种栈Block

    • 和全局Block不一样的地方,在于Block结构体中,有了需要引用的变量,而且在Block结构体初始化的时候,要进行变量的赋值

    • 栈Block对于外部的变量,是以值复制的方式进行访问的。而且在编译阶段就已经完成了赋值,如果外部变量更改值,Block内部的变量值是不变的

    • 在block内进行写操作,编译会报错,提示需要使用__block进行修饰。__block的修饰原理,在下面会有说明。

堆Block(NSMallocBlock)
  1. 强引用Block或者copy后的block,都是堆Block,代码如下
    //堆block NSMallocBlock
    -(void)mallocBlock1{
      int age = 10;
      //默认是强引用
      void(^mallocBlock)(void) = ^{
          NSLog(@">>>>>NSMallocBlock<<<<<, age : %d",age);
      };
    
      NSLog(@"当前的block类型为>>>>>%@",mallocBlock);
      mallocBlock();
    }
    ------------------------------------------------------
    //打印结果为:
    2022-04-21 14:39:23.197179+0800 suanfaProject[3997:179623] 当前的block类型为>>>>><__NSMallocBlock__: 0x6000011b4120>
    2022-04-21 14:39:23.197343+0800 suanfaProject[3997:179623] >>>>>NSMallocBlock<<<<<, age : 10
    
  2. NSMallocBlock 类型的 block 通常不会在源码中直接出现,因为默认它是当一个 block 被 copy 的时候,才会将这个 block 复制到堆中。以下是一个 block 被 copy 时的示例代码 (来自 这里),可以看到,在图中框起来的代码,目标的 block 类型被修改为_NSConcreteMallocBlock。
block的复制.png

Block的本质

  1. 前面说过了Block的分类,并且编译出了各个分类的代码,在编译出来的代码中,最主要的代码就是下面的这些:

    block结构体.png

    在说明堆Block类型的时候,截图的代码中,是有一个void *_Block_copy(const void *arg)方法。三种类型的Block在使用的过程中,都会调用这个方法。其在方法的第一步就是struct Block_layout *aBlock;,获取到对应的Block,而Block_layout结构体就是Block的数据结构。Block_layout结构体展示如下:

     #define BLOCK_DESCRIPTOR_1 1
    struct Block_descriptor_1 {
        uintptr_t reserved;
        uintptr_t size;
    };
    
    #define BLOCK_DESCRIPTOR_2 1
    struct Block_descriptor_2 {
        // requires BLOCK_HAS_COPY_DISPOSE
        BlockCopyFunction copy;
        BlockDisposeFunction dispose;
    };
    
    #define BLOCK_DESCRIPTOR_3 1
    struct Block_descriptor_3 {
        // requires BLOCK_HAS_SIGNATURE
        const char *signature;
        const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
    };
    
    struct Block_layout {
        void * __ptrauth_objc_isa_pointer isa;
        volatile int32_t flags; // contains ref count
        int32_t reserved;
        BlockInvokeFunction invoke;
        struct Block_descriptor_1 *descriptor;
        // imported variables
    };
    
  1. 在各个类型的Block打印中,可以看到各自对应的结构体类型

    • 全局Block -----> NSGlobalBlock

    • 栈Block. -----> NSStackBlock

    • 堆Block -----> NSMallocBlock

  2. 在Block开放的源码中,data.m文件中可以发现,上述的3种Block都是继承自NSObject

Block的类.png
  1. 综上

    1. block底层就是一个Block_layout类型的结构体,这个结构体中包含一个isa指针,本质上是一个OC对象

    2. block是封装了函数调用以及函数调用环境OC对象

Block变量截获

  1. 为什么要进行变量的捕获?

    • 经过前面Block类型的说明,了解到Block的三种类型,分别存放在内存全局区、内存栈区、内存堆区,不同的类型,销毁的时机不同。

    • 在不同的Block内部使用外部变量的时候,需要考虑外部变量的生命周期,特别是销毁的情况

    • 不能出现在Block内部使用的使用,外部变量已经销毁。

    • 所以需要把外部变量捕获到Block的内部,这样的话,使用的时候就不需要管外部的局部变量是不是还存在,只要使用内部的捕获变量就行

  2. C语言中变量分为三类

    • 全局变量: 作用域在全局,哪个地方都能调用

    • 局部变量:作用域在大括号中,只能在大括号内调用

      • 局部自动变量 auto 关键字修饰

      • 局部静态变量 static 关键字修饰

  3. 变量是对象的情况下,也是一样的。对象类型的局部变量连所有权修饰符(__strong,__weak)一起捕获

    • 栈Block:不管外部变量是强引用还是弱引用block都会弱引用访问对象

    • 堆Block:

      • 如果外部强引用block内部也是强引用

      • 如果外部弱引用block内部也是弱引用

  4. 下面展示不同变量在Block内部的引用情况

    • auto变量在Block内部的捕获情况
    auto变量.png
    • 静态变量在Block内部的捕获情况
    静态变量.png
    • 全局变量在Block内部的捕获情况

      全局变量.png
  1. 由第四条得处一下结论

    变量捕获.png

__block修饰符

  1. 前面在Block分类里说过,如果要在Block内部修改引用的外部变量的值,直接修改是不允许的。编译系统给的提示是需要使用__Block修饰符。接下来就看一下__Block的逻辑。

    __block修饰符.png
  1. 在报错的代码上进行修改,外部变量增加__block修饰,并且在Block内部修改变量的值。

    //堆block NSMallocBlock
    -(void)mallocBlock1{
       //在Block需要引用的外部变量,添加__Block修饰符
       __block int age = 10;
       //默认是强引用
       void(^mallocBlock)(void) = ^{
         //在Block内部修改变量的值
           age = 30;
           NSLog(@">>>>>NSMallocBlock<<<<<, age : %d",age);
       };
       NSLog(@"当前的block类型为>>>>>%@",mallocBlock);
       mallocBlock();
    }
    ------------------------------------------------------
    打印结果为:
      2022-04-22 14:26:14.442743+0800 suanfaProject[2562:85326] 当前的block类型为>>>>><__NSMallocBlock__: 0x60000092ebe0>
    2022-04-22 14:26:14.442810+0800 suanfaProject[2562:85326] >>>>>NSMallocBlock<<<<<, age : 30
    

    可以看出,加了__block后,编译不会报错,并且,修改变量的值成功。

  2. 接下来就编译出这段代码的.cpp文件进行查看

    // @implementation BlockClang
    //编译器将__block修饰的外部变量包装成一个结构体(对象),在结构体中创建一个同名变量,
    struct __Block_byref_age_0 {
        void *__isa;
      //结构体的指针指向
        __Block_byref_age_0 *__forwarding;
        int __flags;
        int __size;
      //同名变量
        int age;
    };
    //Block的结构体,这个结构体就是block的实际内容。
    struct __BlockClang__mallocBlock1_block_impl_0 {
        struct __block_impl impl;
        struct __BlockClang__mallocBlock1_block_desc_0* Desc;
    
        //block内部捕获根据外部变量创建的结构体指针
        //将结构体copy到堆上,在block中使用自动变量时,使用指针指向的结构体中的自动变量,于是就达到了修改外部变量的作用。
        __Block_byref_age_0 *age; // by ref
    
        //block结构体的同名构造
        __BlockClang__mallocBlock1_block_impl_0(void *fp,struct __BlockClang__mallocBlock1_block_desc_0 *desc,__Block_byref_age_0 *_age,int flags=0) : age(_age->__forwarding) {
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
        }
    };
    //-(void)mallocBlock1的方法,编译出来的文件
    static void _I_BlockClang_mallocBlock1(BlockClang * self, SEL _cmd) {
        //把__block修饰的变量包装成__Block_byref_age_0结构体,并且玩层指针和变量的赋值
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
    
      //获取实例化的block
        void(*mallocBlock)(void) = ((void (*)())&__BlockClang__mallocBlock1_block_impl_0((void *)__BlockClang__mallocBlock1_block_func_0, &__BlockClang__mallocBlock1_block_desc_0_DATA,(__Block_byref_age_0 *)&age,570425344));
    
        //打印语句
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_e5501c_mi_1,mallocBlock);
        //block的调用
        ((void (*)(__block_impl *))((__block_impl *)mallocBlock)->FuncPtr)((__block_impl *)mallocBlock);
    }
    
    //block内部任务创建出来的方法
    static void __BlockClang__mallocBlock1_block_func_0(struct __BlockClang__mallocBlock1_block_impl_0 *__cself) {
        __Block_byref_age_0 *age = __cself->age; // bound by ref
    
        //这里是给__block修饰的外部变量的赋值操作,可以看出使用的是根据变量的指针找到内存地址,然后进行的赋值
        (age->__forwarding->age) = 30;
      //打印操作
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_e5501c_mi_0,(age->__forwarding->age));
    }
    
    /*
    //以下代码暂不关注
    static struct __BlockClang__mallocBlock1_block_desc_0 {
        size_t reserved;
        size_t Block_size;
        void (*copy)(struct __BlockClang__mallocBlock1_block_impl_0*,
                     struct __BlockClang__mallocBlock1_block_impl_0*);
        void (*dispose)(struct __BlockClang__mallocBlock1_block_impl_0*);
    } __BlockClang__mallocBlock1_block_desc_0_DATA = { 0,
        sizeof(struct __BlockClang__mallocBlock1_block_impl_0),
        __BlockClang__mallocBlock1_block_copy_0,
        __BlockClang__mallocBlock1_block_dispose_0
    };
    
    
    static void __BlockClang__mallocBlock1_block_copy_0(struct __BlockClang__mallocBlock1_block_impl_0*dst, struct __BlockClang__mallocBlock1_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8);}
    
    static void __BlockClang__mallocBlock1_block_dispose_0(struct __BlockClang__mallocBlock1_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8);}
    */
    
    // @end
    
  3. 对比一下没有使用__block的编译文件

    使用__block的对比.png
    • 编译器会将__block修饰的变量包装成一个结构体(对象)

    • 在结构体中新建一个同名变量

    • block内部捕获该结构体指针

    • 在block中使用外部变量时,使用指针指向的结构体中的外部变量

  4. 在对结构体中的指针指向进行说明

    • 如果Block是一个栈Block(NSStackBlock),结构体中的__forwarding指针指向的就是自己所在区域的地址

    • 如果Block是一个堆Block(NSMallocBlock),由于堆Block是栈Block复制生成的,即从栈区复制到了堆区。则在堆区会有一块属于Block的空间,也就会有变量结构体的空间。这个情况下,__forwarding指针指向的在堆区的内存地址。

      • a、当block在栈时,__Block_byref_a_0结构体内的__forwarding指针指向结构体自己
      • b、当block被复制到堆中时,栈中的__Block_byref_age_0结构体也会被复制到堆中一份,而此时栈中的__Block_byref_a_0结构体中的__forwarding指针指向的就是堆中的__Block_byref_a_0结构体,堆中__Block_byref_a_0结构体内的__forwarding指针依然指向自己
      • c、通过__forwarding指针巧妙的将修改的变量赋值在堆中的__Block_byref_a_0中


        __block的 __forwarding指针.png

block循环引用以及解决方法

循环引用的原因

  1. 引用计数算法(Reference Counting),对每个对象保存一个整型的引用计数器属性。用于记录对象被引用的情况。

    • 对于一个对象 A,只要有任何一个对象引用了 A,则 A 的引用计数器就加1;

    • 当引用被销毁时,引用放发通知给对象A,引用计数器就减1。

    • 对象 A 的引用计数器一旦变为0,即表示对象 A 不可能再被使用,对象A被销毁。

  2. 正常释放流程

    • 当A持有B,当A销毁后,会给B发送信号。B收到信号后,如果此时B的引用计数为0时,则B就会销毁,此时A,B都能正常释放。不会引起内存泄漏
    正常引用.jpg
  1. 循环引用

    • 当A持有B,B同时也持有A时,此时A销毁需要B先销毁,而B销毁同样需要A先销毁,就导致相互等待销毁,此时A,B的引用计数都不为0,所以A,B此时都无法释放。 从而导致了内存泄漏
    循环引用.jpg

解决方法

  1. 平常代码里用的最多的应该就是__weak修饰符。

    • 定义一个弱引用的weakSelf,即__weak typeof(self) weakSelf = self;,指向self

    • 因为是__weak修饰,变量的引用计数不会增加

    • 使用__weak修饰的代码展示

      - (void)weakSelf{
          self.name = @"jack";
          __weak typeof(self) weakSelf = self;
          void(^block)(void) = ^{
              NSLog(@"weakSelf.name>>>>>%@",weakSelf.name);
          };
          block();
      }
      ------------------------------------------------------
      打印结果为:
      2022-04-24 10:24:20.008072+0800 suanfaProject[1716:30969] weakSelf.name>>>>>jack
      
  2. weak-strong-dance (弱强共舞)

    • 如果只使用__weak修饰的变量,其引用计数不会增加。这样的话,会出现block内部持有的对象被提前释放的情况。比如在Block内部执行一个计时器任务,就会出现定时任务在执行的时候,退出控制器,控制器就被销毁,其变量获取不到

        - (void)weakSelfShortcoming{
          self.name = @"jack";
          __weak typeof(self) weakSelf = self;
          void(^block)(void) = ^{
              dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                  NSLog(@"weakSelf.name>>>>>%@",weakSelf.name);
              });
          };
          block();
      }
      ------------------------------------------------------
      2022-04-24 10:28:11.588892+0800 suanfaProject[1768:33723] ~~~ self dealloc ~~~
        //可以看出,self已经被销毁,self的地址清空,也就获取不到weakSelf.name
      2022-04-24 10:28:11.936690+0800 suanfaProject[1768:34131] weakSelf.name>>>>>(null)
      
    • Block内部嵌套定时任务,则需要同时使用__weak__strong。如果只用weak修饰,则可能出现block内部持有的对象被提前释放,为了防止block内部变量被提前释放,使用__strong对引用计数+1,防止提前释放。

        - (void)weak_strong_Self{
          self.name = @"jack";
          __weak typeof(self) weakSelf = self;
          void(^block)(void) = ^{
            //在Block内部,使用__strong修饰
              __strong typeof(weakSelf)strongSelf = weakSelf;
              dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                  NSLog(@"weakSelf.name>>>>>%@",strongSelf.name);
              });
          };
          block();
      }
      ------------------------------------------------------
        //可以看出,self是在定时任务完成以后进行销毁的
      2022-04-24 10:35:18.498441+0800 suanfaProject[1869:38834] weakSelf.name>>>>>jack
      2022-04-24 10:35:18.499136+0800 suanfaProject[1869:38688] ~~~ self dealloc ~~
      
  3. __block修饰变量

    • 代码如下
         - (void)blockSymbol{
          self.name = @"jack";
          __block BlocksVC *blockVC = self;
          void(^block)(void) = ^{
              NSLog(@"blockVC.name>>>>>%@",blockVC.name);
          };
          block();
      }
      ------------------------------------------------------
      2022-04-24 10:48:00.279485+0800 suanfaProject[2049:46359] blockVC.name>>>>>jack
      2022-04-24 10:48:00.625749+0800 suanfaProject[2049:46303] ~~~ self dealloc ~~~
      
  4. 把对象作为Block的参数,传到Block内部

    • 代码如下

         self.name = @"jack";
         void(^block)(BlocksVC *blockVC) = ^(BlocksVC *blockVC){
             dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                 NSLog(@"blockVC.name>>>>>%@",blockVC.name);
             });
         };
         block(self);
      }
      ------------------------------------------------------
      2022-04-24 10:56:38.622366+0800 suanfaProject[2154:51224] blockVC.name>>>>>jack
      2022-04-24 10:56:38.623024+0800 suanfaProject[2154:51097] ~~~ self dealloc ~~~</pre>
      
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容