• # iOS Block的本质(三)


    iOS Block的本质(三)

    • 上一篇文章iOS Block的本质(二)中已经介绍过block变量的捕获,本文继续探寻block的本质。

    1. block对对象变量的捕获,ARC 环境

    1. block一般使用过程中都是对对象变量的捕获,那么对象变量的捕获同基本数据类型变量相同吗?

    2. 查看一下代码思考:当在block中访问的为对象类型时,对象什么时候会销毁?

      // ARC环境下代码
      typedef void (^Block)(void);
      int main(int argc, const char * argv[]) {
          @autoreleasepool {
              Block block;
              {
                  Person *person = [[Person alloc] init];
                  person.age = 10;
      
                  block = ^{
                      NSLog(@"------block内部%d",person.age);
                  };
              } // 执行完毕,person没有被释放
              NSLog(@"--------");
          } // person 释放
          return 0; 
      }
      
    3. 大括号执行完毕之后,person依然不会被释放。上一篇文章提到过,person为aotu变量,传入的block的变量同样为person,即block有一个强引用引用person,所以block不被销毁的话,peroson也不会销毁。

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

    2. block对对象变量的捕获,ARC 环境

    1. 在MRC环境下即使block还在,person却被释放掉了。因为MRC环境下block在栈空间,栈空间对外面的person不会进行强引用。

      //MRC环境下代码
      int main(int argc, const char * argv[]) {
          @autoreleasepool {
              Block block;
              {
                  Person *person = [[Person alloc] init];
                  person.age = 10;
                  block = ^{
                      NSLog(@"------block内部%d",person.age);
                  };
                  [person release];
              } // person被释放
              NSLog(@"--------");
          }
          return 0;
      }
      
    2. block调用copy操作之后,person不会被释放。

      block = [^{
         NSLog(@"------block内部%d",person.age);
      } copy];
      
    3. 上文中也提到过,只需要对栈空间的block进行一次copy操作,将栈空间的block拷贝到堆中,person就不会被释放,说明堆空间的block可能会对person进行一次retain操作,以保证person不会被销毁。堆空间的block自己销毁之后也会对持有的对象进行release操作。

    4. 也就是说栈空间上的block不会对对象强引用,堆空间的block有能力持有外部调用的对象,即对对象进行强引用或去除强引用的操作。

    5. 在__weak修饰之后,person在作用域执行完毕之后就被销毁了。

      typedef void (^Block)(void);
      int main(int argc, const char * argv[]) {
          @autoreleasepool {
              Block block;
              {
                  Person *person = [[Person alloc] init];
                  person.age = 10;
      
                  __weak Person *weakPerson = person;
                  block = ^{
                      NSLog(@"------block内部%d",waekPerson.age);
                  };
              }
              NSLog(@"--------");
          }
          return 0;
      }
      
    6. 将代码转化为c++来看一下上述代码之间的差别。

      • 在转换C++代码时用__weak修饰变量,需要告知编译器使用ARC环境及版本号否则会报错,添加说明-fobjc-arc -fobjc-runtime=ios-8.0.0
      • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
       struct __main_block_impl_0 {
         struct __block_impl impl;
         struct __main_block_desc_0* Desc;
         Person *__weak person;
         __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _person, int flags=0) : person(_weakPerson) {
           impl.isa = &_NSConcreteStackBlock;
           impl.Flags = flags;
           impl.FuncPtr = fp;
           Desc = desc;
         }
       };
      
    7. 用__weak修饰的变量,在生成的__main_block_impl_0中也是使用__weak修饰捕获。

    3. __main_block_copy\_0__main_block_dispose_0函数分析:

    1. 当block中捕获对象类型的变量时,我们发现block结构体__main_block_impl_0的描述结构体__main_block_desc_0中多了两个参数copy和dispose函数,查看源码:

    2. copy和dispose函数中传入的都是__main_block_impl_0结构体本身。

    3. copy本质就是__main_block_copy_0函数,__main_block_copy_0函数内部调用_Block_object_assign函数,_Block_object_assign中传入的是person对象的地址,person对象,以及8。

    4. dispose本质就是__main_block_dispose_0函数,__main_block_dispose_0函数内部调用_Block_object_dispose函数,_Block_object_dispose函数传入的参数是person对象,以及8。

    4. _Block_object_assign函数调用时机及作用

    1. 当block进行copy操作的时候就会自动调用__main_block_desc_0内部的__main_block_copy_0函数,__main_block_copy_0函数内部会调用_Block_object_assign函数。

    2. _Block_object_assign函数会自动根据__main_block_impl_0结构体内部的person是什么类型的指针,对person对象产生强引用或者弱引用。可以理解为_Block_object_assign函数内部会对person进行引用计数器的操作,如果__main_block_impl_0结构体内person指针是__strong类型,则为强引用,引用计数+1,如果__main_block_impl_0结构体内person指针是__weak类型,则为弱引用,引用计数不变。

    5. _Block_object_dispose函数调用时机及作用

    1. 当block从堆中移除时就会自动调用__main_block_desc_0中的__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose函数。

    2. _Block_object_dispose会对person对象做释放操作,类似于release,也就是断开对person对象的引用,而person究竟是否被释放还是取决于person对象自己的引用计数。

    6. 总结

    1. 一旦block中捕获的变量为对象类型,block结构体中的__main_block_desc_0会出两个参数copy和dispose。因为访问的是个对象,block希望拥有这个对象,就需要对对象进行引用,也就是进行内存管理的操作。比如说对对象进行retarn操作,因此一旦block捕获的变量是对象类型就会会自动生成copy和dispose来对内部引用的对象进行内存管理。

    2. 当block内部访问了对象类型的auto变量时,如果block是在栈上,block内部不会对person产生强引用。不论block结构体内部的变量是__strong修饰还是__weak修饰,都不会对变量产生强引用。

    3. 如果block被拷贝到堆上。copy函数会调用_Block_object_assign函数,根据auto变量的修饰符(__strong,__weak,unsafe_unretained)做出相应的操作,形成强引用或者弱引用

    4. 如果block从堆中移除,dispose函数会调用_Block_object_dispose函数,自动释放引用的auto变量。

    7. 拓展

    1. 下列代码person在何时销毁?在person 类中重写 delloc 方法

      - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
      {
          Person *person = [[Person alloc] init];
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
              NSLog(@"%@",person);
          });
          NSLog(@"touchBegin----------End");
      }
      
      // Log : 打印结果 : touchBegin----------End
      // Log :打印结果 : <Person: 0x6080000080d0>
      // Log :打印结果 : person 对象调用 delloc
      
      • 上文提到过ARC环境中,block作为GCD API的方法参数时会自动进行copy操作,因此block在堆空间,并且使用强引用访问person对象,因此block内部copy函数会对person进行强引用。当block执行完毕需要被销毁时,调用dispose函数释放对person对象的引用,person没有强指针指向时才会被销毁。
    2. 下列代码person在何时销毁 ?

      - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
      {
          Person *person = [[Person alloc] init];
      
          __weak Person *weakPerson = person;
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
              NSLog(@"%@",weakPerson);
          });
          NSLog(@"touchBegin----------End");
      }
      // Log : 打印结果 : touchBegin----------End
      // Log :打印结果 : person 对象调用 delloc
      // Log :打印结果 : null
      // 结果是 person 先销毁才执行block 打印结果为 null
      
      • block中对weakPerson为__weak弱引用,因此block内部copy函数会对person同样进行弱引用,当大括号执行完毕时,person对象没有强指针引用就会被释放。因此block块执行的时候打印null。
    3. 通过示例代码进行总结。

      - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
      {
          Person *person = [[Person alloc] init];
      
          __weak Person *weakPerson = person;
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      
              NSLog(@"weakPerson ----- %@",weakPerson);
      
              dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                  NSLog(@"person ----- %@",person);
              });
          });
          NSLog(@"touchBegin----------End");
      }
      // Log : 打印结果 : touchBegin----------End
      // Log :1s 后 打印结果 : weakPerson -> <Person: 0x608000006050>
      // Log :3s 后 打印结果 : person -> <Person: 0x608000006050>
      // Log :打印结果 : person 对象调用 delloc
      
      - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
      {
          Person *person = [[Person alloc] init];
      
          __weak Person *waekP = person;
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      
              NSLog(@"person ----- %@",person);
      
              dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                  NSLog(@"weakP ----- %@",waekP);
              });
          });
          NSLog(@"touchBegin----------End");
      }
      // Log : 打印结果 : touchBegin----------End
      // Log :1s 后 打印结果 : person -> <Person: 0x608000006050>
      // Log :打印结果 : person 对象调用 delloc
      // Log :3s 后 打印结果 : weakPerson -> null
      
  • 相关阅读:
    三.php变量赋值和销毁
    四.php运算符(1)算术运算符
    四.php运算符(2)比较运算符
    四.php运算符(4)逻辑运算符
    extjs与后台交互
    EXT2.0 form实例
    SQL Server 2005 中的计算字段
    extJSitemselector的使用
    操作二进制数据
    Extjs Itemselector的使用示例
  • 原文地址:https://www.cnblogs.com/ShaoYinling/p/10249862.html
Copyright © 2020-2023  润新知