当前位置:首页 » 《关注互联网》 » 正文

oc中block的本质及底层原理_拥有大厂梦的OC开发的博客

2 人参与  2021年08月30日 09:43  分类 : 《关注互联网》  评论

点击全文阅读


  • block的本质

  • block的种类及储存区域

  • __block的本质

  • block的循环引用

前言:

这里就不讨论block的具体写法及使用场景了,因为当你有一天想深入了解block 的底层原理时,你早已把block写了几十遍了。

一、block的本质:

block是带有自动变量的匿名函数。

注:

        局部变量  = 自动变量(栈区)+ 静态局部变量 (全局区)

        这里说的自动变量是指block里面捕获的外部局部变量,当然你也可以不捕获

二、block的种类及储存区域:

NSGlobalBlock :存放在全局区的block

        只有当block里面仅仅捕获了外部的静态局部变量、全局变量、静态全局变量时,该block 存.     放在全局区。当然,如果block里面什么类型的外部变量都不捕获时,它也在全局区。 

NSStackBlock : 存放在栈区的block

NSMallocBlock : 存放在堆区

        在arc下,截获了外部自动变量的block被创建出来时存放在栈区,然后如果该block被strong/copy 修饰符修饰时,系统会将该block从栈区,copy一份到堆区,并将指针指向堆区的block。

        而系统默认的就是strong类型(不管是成员变量block还是局部变量block ),所以在大部分情况下解惑了外部自动变量的block都是存放在堆区,只有当你手动的将一个局部block修饰weak属性时,它才不会被copy到堆区。

注:ios的五大内存区域:栈区、堆区、全局区、常量区、代码区

三、__block的本质

先说一下block截获对象的性质

  1. 局部变量:
    1. 自动变量:
      1. 基本类型:截获其值
      2. 对象类型:截获指针
    2. 静态局部变量:截获指针
  2. 静态全局变量:截获指针
  3. 全局变量:截获指针
  4. 成员变量:截获指针

这里就可以看到,只有当截获自动变量的基本类型数据(如int)时,block只截获其值。当然,如果你只截获到其值,并没有拿到它的内存地址,在block里面你就不能对它进行修改(如+1)。

需要特别注意的是: 

strong/copy 修饰的block代码块里面截获对象的时候,会持有该对象,使其引用计数+1。

__block的本质

当我们截获了自动变量的基本类型数据,又想在block里面对它进行修改时,这个时候__block就派上用场了。

看一下基本类型数据用__block修饰之后的源码

__block int i = 0;

源码:
struct __Block_byref_i_0 {
  void *__isa;  //isa指针
__Block_byref_i_0 *__forwarding; //该指针指向自身
 int __flags;  //标记
 int __size;  //大小
 int i; // 变量值
};
//新人只看上面这块就行了

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref

        (i->__forwarding->i) ++;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_3b0837_mi_0,(i->__forwarding->i));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

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

static struct __main_block_desc_0 {
  size_t reserved;
  size_t 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(int argc, const char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};

    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

从源码中我们了解到,系统将带有__block的变量转换成一个block结构体。并且当block被copy到堆上的时候,一并把_block变量的结构体也copy一份到堆中。其中堆中block结构体的_forwarding指针指向其变量本身。

这样,block里面就能截获到__block变量的结构体里面的__forwarding指针,而该指针指向了堆上_block变量。这样,block就能对该变量进行修改了。

block的循环引用

首先我们来看一段代码

//block1,block2是self的成员变量,block3是person类的成员变量
//会造成循环引用
self.block1 = ^{
    NSLog(@"%@",self);
};

//同样也会造成循环引用
self.block1 = ^{
    NSLog(@"%@",_blcok2);
};

//同样也会造成循环引用
Person *person = [[Person alloc] init];
person.block3 = ^{
    NSLog(@"%@",person);
};

让我们来看一下这个循环引用究竟是怎么产生的

看第一个,block1是self里的成员变量,那么self持有了block1,强引用,引用计数+1.然后看到block1代码里面引用了self,上面已经说了,copy/strong(默认)修饰的block里面都会持用,强引用截获的对象,这样就形成了一个闭环self->block1->self。从而导致self和block1互相引用,不会被自动释放。

再看第二个,_block2等价于self->block2,那么就形成了self->block1->self->block2,很明显,这也是一个闭环,也就是循环引用。

再看第三个,我们就能知道第三个也形成了一个闭环person->block3->person

所以说导致循环引用的罪魁祸首并不是self,而是要看是否产生互相强引用,self本无过,只是我们在开发过程中常出现的循环引用的闭环里都包含了self。

那么如何避免循环引用呢?

既然他们是因为互相强引用导致的,那我们就把其中一个改为弱引用就好了

__weak __typeof(self) weakSelf = self; //声明一个弱引用指针

self.block1 = ^{
    NSLog(@"%@",weakSelf);
};

这时,我们再来看一下他们之间的引用,self->block1->weakSelf。

由于此时,weakSelf是一个弱引用,当它再指向它的成员变量block1的时候已经不是强引用了,这时强引用循环就断开了。

当然,如果我们在block里面仅仅调用了局部变量,也就不需要给self声明弱引用指针了。

参考文章:

https://www.jianshu.com/p/ee9756f3d5f6

https://blog.csdn.net/olsQ93038o99S/article/details/83829415

https://blog.csdn.net/jingqiu880905/article/details/51997126

https://www.jianshu.com/p/53cedd7bafa4


点击全文阅读


本文链接:http://zhangshiyu.com/post/26552.html

变量  引用  截获  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1