iOS之Block本质(三)

首先,我们看两段代码:
在这里插入图片描述从运行结果可以看出,如果是普通局部变量age,第17行和第22行的age地址是一样的,第20行的地址跟前面两个是不同的。
这个原因我们在上节已经分析过。是因为:
第17和第22行的age是age的地址
第20行的age是捕获进去的age,其是一个在block内部新建的同名age
,因此,地址不同。

在这里插入图片描述转化为底层代码可以看到:
在这里插入图片描述
从底层代码可以看出,三者最后都是取的&(age.__forwarding->age),也就是被包装为__Block_byref_age_0类型后里面的age。可以解释2—和3—地址一样。

按说1—的地址应该也是一样的,不知为何1—的地址不同

上面两端代码都是在ARC环境下进行的,是不是ARC又为我们做了哪些操作,从而造成这样的结果呢?
我们分别将两段代码运行在MRC上

在这里插入图片描述
这个结果跟在ARC上的结果是一致的,不再谈论

在这里插入图片描述
这个结果就跟我们跟在ARC上的结果是不同的,同样,我们看下其底层源码:

在这里插入图片描述

源码也一样,怎么结果就是不一样呢?到底ARC为我们做了什么?

我们继续做实验

在这里插入图片描述
在ARC下,我们添加了几个打印,可以看出在ARC下,blcok的类型是NSMallocBlock,存储在堆上的。

修改为MRC后:
在这里插入图片描述
不一样的地方出现了,在MRC下block的类型是NSStackBlock类型的,存储在栈上。

按照我们之前的结论:
访问auto变量的block是NSStackBlock类型的。
将^{}block赋值给强指针YZBlockL类型的block,也就是 ^{}block有强指针引用,因此,在ARC下 ^{}block 会自动将栈上的block复制到堆上,所以,block的类型是NSMallocBlock

也就三个打印的age都是&(age.__forwarding->age)的地址。
但是1—是在栈上的age。2—和3—都是访问的堆上面的age。


上面的代码,也可以看出,使用__block修饰的age变量,底层的时候,也调用了copy和dispose相关函数,并在copy函数里面通过__Block_object_assign使用&dst->age,访问age,并对age进行强引用操作。而&dst就是__main_block_impl_0,其里面的age就是__Block_byref_age_0 *age。
换句话说,就是使用_block修饰的变量,底层会进行copy操作,并在copy操作中对里面转换为__Block_byref_age_0类型的age进行相应的内存管理。

__block的内存管理

当block在栈上时,并不会对__block变量产生强引用

当block被copy到堆上时
会调用block内部的copy函数
copy函数内部会调用__Block_object_assign函数
__Block_object_assign函数会对__block修饰的变量进行内存管理

在这里插入图片描述
这个图表明,在进行copy操作后,block会从栈复制到堆上面。同时,会将block内部访问的使用__block修饰的变量也复制到堆上面,并且,堆上面的block还是会对堆上面的__block修饰的变量进行引用关系。

如果,有两个block,同时引用一个__block修饰的变量呢?
在这里插入图片描述
两个block同时引用一个__block修饰的变量,会对__block进行相当于+2的操作

在这里插入图片描述运行结果

2020-04-28 16:55:09.859862+0800 block学习[5641:192209] 1----0x7ffeefbff4f8
2020-04-28 16:55:09.860258+0800 block学习[5641:192209] 2----0x100703b48
2020-04-28 16:55:09.860365+0800 block学习[5641:192209] ---30---
2020-04-28 16:55:09.860406+0800 block学习[5641:192209] 3----0x100703b48
2020-04-28 16:55:09.860437+0800 block学习[5641:192209] ---30---
2020-04-28 16:55:09.860465+0800 block学习[5641:192209] 4----0x100703b48

可以看到2、3、4的age都是同一个内存地址。

在这里插入图片描述
在这里插入图片描述上面两个图可以看到,一个是__main_block_impl_0类型,一个是__main_block_impe_1类型,但是从定义可以看出

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_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;
  }
};

struct __main_block_impl_1 {
  struct __block_impl impl;
  struct __main_block_desc_1* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

里面的age统一都是__Block_byref_age_0类型,因此,上图里面&dst->age访问的age都是同一个,是__Block_byref_age_0类型里面的age。

当block从堆中移除的时
会调用block内部的dispose函数
dispose函数内部会调用__Block_object_dispose函数
__Block_object_dispose函数会自动释放引用的__block修饰的变量

在这里插入图片描述
在这里插入图片描述


__block的__forwarding指针

在__block修饰变量的几次例子中,我们都看到,最后访问里面的age是使用age.__forwarding->age访问的方式,这是为什么呢?
首先,我们知道__forwarding指向的就是他自己,那么, 为什么不直接访问里面的age变量,而是通过__forwarding指针访问age变量呢?

这是因为,如果我们访问的age变量在栈上的时候,通过__forwarding找到它自己,然后访问里面的age,很容易找到age,这是行的通的,虽然复杂了一步。
但是,如果block已经复制到堆上面,__forwarding的作用就突显出来了。__forwarding指针会指向堆上面的block,这样里面访问的__block修饰的变量,确保了是堆上面的。

注:复制并不是剪切,栈上面是还有的。

在这里插入图片描述


前面我们讲的是__block修饰基础变量类型,如果用__block修饰对象类型的变量,将会是怎样的情形呢?

__block修饰对象类型

在这里插入图片描述

代码通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-12.0.0 main.m指令,转换为底层代码
部分代码为:
在这里插入图片描述
可以看出,使用__block修饰的对象类型,也会生成一个__Block_byref_person_0类型且变量名为person的变量。在__Block_byref_person_0定义里面,有一个YZPerson类型且变量名为person的变量。
首先,我们看到这个person是__strong修饰的。
那么,如果使用__weak修饰对象的话,会是怎样的情况呢?

在这里插入图片描述首先,我们要明确一点,__weak是修饰person对象的,而跟__block无关。这也就解释了为何__block __weak int age = 10;会有一个警告。因为__weak只能修饰对象。
转换为底层代码可以看到:
在这里插入图片描述可以看到,__weak是修饰的YZPerson类型的person对象,而跟__Block_byref_weakPerson_0类型无关。
也就是__block修饰的对象生成的__Block_byref_weakPerson_0类型,都是强引用。而__Block_byref_weakPerson_0里面的YZPerson类型的person对象是强还是弱,跟外部使用的强或弱有关。

在这里插入图片描述


继续研究发现
在这里插入图片描述__Block_byref_weakPerson_0内部也有一个__Block_byref_id_object_copy和__Block_byref_id_object_dispose函数。这是使用__block修饰基本数据类型所没有的两个新东西。
那么,这两个新东西是什么呢?

我们知道,当block被复制到堆上时,__block修饰的变量也会被拷贝到堆上面去,当__block修饰的变量被拷贝到堆上时,内部就会调用上图两个函数,对其进行内存管理相关操作。

在对__block修饰的变量进行转换的时候,我们可以看到:

__block __weak YZPerson *weakPerson = person;
转换为:

__attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __Block_byref_weakPerson_0 weakPerson = {
(void*)0,
(__Block_byref_weakPerson_0 *)&weakPerson, 
33554432, 
sizeof(__Block_byref_weakPerson_0), 
__Block_byref_id_object_copy_131, 
__Block_byref_id_object_dispose_131, 
person
};

对应
struct __Block_byref_weakPerson_0 {
  void *__isa;
__Block_byref_weakPerson_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 YZPerson *__weak weakPerson;
};

可以看到__Block_byref_id_object_copy_131传给了 void (* __Block_byref_id_object_copy)(void*, void*);
__Block_byref_id_object_dispose_131, 传给了 void (* __Block_byref_id_object_dispose)(void*);

通过定义可以看到:
在这里插入图片描述
可以看到,其内部也有一个__Block_object_assign或__Block_object_dispose函数。并且里面有一个dst+40的操作,dst+40后是谁呢?
在这里插入图片描述可以看出,dst+40正好指向的是YZPerson类型的weakPerson对象。这样就看出,__Block_byref_id_object_copy和__Block_byref_id_object_dispose是对YZPerson类型的weakPerson对象进行内存管理的。

当__block变量在栈上时,不会对指向的对象产生强引用

当__block变量被copy到堆时
会调用__block变量内部的copy函数
copy函数内部会调用__Block_object_assign函数
__Block_object_assign函数会根据所指向对象的修饰符(__strong\__weak\__unsafe_unretain)做出相应的操作,形成强引用或弱引用(注意:ARC才会强或弱,MRC只会是弱)

如果__block变量从堆上移除
会调用__block变量内部的dispose函数
dispose函数内部会调用__Block_object_dispose函数
__Block_object_dispose函数会自动释放指向的对象

关于上面注意里面的话,我们可以举一个在MRC下的例子:
在这里插入图片描述
在23行,block还没释放,person就已经被释放了,这说明:
在MRC下,__block后面修饰的变量是强或者弱都没有关系,最后block对__block修饰的变量都是弱引用


block的循环引用问题

首先,看一段代码
在这里插入图片描述
可以看到,在19行结束后,也没有打印person消失的信息,说明person没有消失,被循环引用了。

在这里插入图片描述大致就是,block内部其实是有一个强指针引用了YZPerson类型的person对象,指向着YZPerson。而YZPerson对象里面有一个_block的成员变量,指向着对应的block,这就产生了循环引用问题。

左上角的block是指的16行等号右边的block
右边的YZPerson(MJPerson)是指的14行等号右边的东西
左下角的person是指的14行左边的内容


在ARC下解决循环引用一般使用__weak或者__unsafe_unretained
两者的区别是:
__weak会在指向的对象消失的时候,将指针置为nil;
__unsafe_unretained会在指向的对象消失的时候,不会将指针置为nil;

当然,也可以借助__block来解决循环引用问题
在这里插入图片描述
如果没有person = nil的情况下,其各种关系是如下:
在这里插入图片描述
__block指的是:__block修饰的person
对象指的是:(__block修饰的person)里面的YZPerson类型的person对象
Block指的是:person.block(),是YZPerson类型的person对象拥有的成员变量
Block持有__block,是指的16行代码,等号右边的内容

加上一句置为nil的操作后,其关系为:
在这里插入图片描述


在MRC下,可以使用__unsafe_unretained解决循环引用。因为MRC没有__weak,也就不存在使用__weak解决循环引用的问题了。
使用__unsafe_unretained可以解决的原因是,block内部引用对象的时候,不会对引用计数器+1。
另外,我们也可以直接使用__block解决循环引用问题。原因的话,我们在之前有讲过,就是因为,在MRC下,block对__block修饰的变量都是相当于弱引用,不会对引用计数器+1。


参考
iOS中__block 关键字的底层实现原理
Objective-C高级编程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值