美文网首页
Block探究

Block探究

作者: coder_feng | 来源:发表于2019-06-03 22:09 被阅读0次

block的原理是怎样?本质是什么?

block本质上也是一个OC对象,block是封装了函数调用与及调用环境的OC对象,源码实例图:

源码实例图

另外block布局底层结构图如下

布局图

block的变量捕捉(capture)

为了保证block能够正常访问外部的变量,block有个变量捕捉机制

捕捉机制图

auto 变量捕捉

auto变量捕捉图1

另外auto变量的捕捉是值传递,这个和static和全局变量都是不一样的,可以通过clang的源码查看auto和static的区别:

auto static 测试图 源码展现图

可以看到static是指针传递,而auto是值传递,有这样的差异是因为在执行函数之后age就变成了垃圾数据,所以执行block的时候,不可能去访问age的内存,但是static 的内存是一直贯穿整个运行的生命周期的?

全局变量,static全局变量,staitc局部变量

static 全局 局部 源码图

可以看到全局变量并没有捕获到block里面,但是局部的static会,因为局部变量只能在局部中访问,并且是跨域访问,所以为了能访问正确,一定需要捕捉进去,因为全局变量直接在data区域,是可以直接访问的,不需要捕获进去,循环引用也是因为基于这种捕捉情况下产生的,例如

- (void)test{

    void (^block)(void) = ^{

        NSLog(@"-----%p",self);

    }

}

因为OC的机制,test方法会自动携带(id self,SEL _cmd)两个默认参数,所以可以当做是局部变量捕捉进去,当出现block引用self,self又引用block的时候就会导致循环引用了,上面的例子,目前这样看来当然是不会的,这个只是说明是导致循环引用的一个原理而已

对象类型的auto变量捕捉

当block内部访问了对象类型的auto变量时,如果block是在栈上,将不会auto变量产生强引用,如果block被拷贝到堆上,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符(__strong,__weak,__unsafe_unretained)作出相应的操作,形成强引用(retain)或者弱引用,如果block从堆上移除,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量(release)

调用时机图

_Block_object_assign((void *)&dst->a,(void *)src->a,3/*BLOCK_FIELD_IS_BYREF*)

_Block_object_dispose((void *))src->a ,3/*BLOCK_FIELD_IS_BYREF*/);

对象类型的__block变量

_Block_object_assign((void *)&dst->p,(void *)src->p,8/*BLOCK_FIELD_IS_BYREF*/);

_Block_object_dispose((void *)&src-p,8/*BLOCK_FIELD_IS_OBJECT*/);

对象的auto和__block

当__block被拷贝到堆上,会调用__block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符(__strong,__weak,__unsafe_unretained)作出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅仅限于ARC时会retain,MRC时不会retain),如果block从堆上移除,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量(release)

block的类型

block有3中类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承NSBlock类型

__NSGlobalBlock__(_NSConcreteGlobalBlock)

__NSStackBlock__(_NSConcreteStackBlock)

__NSMallocBlock__(_NSConcreteMallocBlock)

block的类型图1 block的类型图2

证明:

block style类型证明图

从图中我们可以看到这和上面的图片描述不一样,原因是什么呢?在解释这个问题之前,我们在看看这个例子:

stackblock图

从上面的图是不是可以悟出什么东西来呀,上面arc的情况下能正常打印结果,说明了arc帮我们额外做了一些操作,例如copy block从栈到堆,打印[block copy] 的值和block的地址值是一样就说明了这一点,另外从clang出来的全部都是satckblock类型的,说明llvm编译器在运行时帮我们做了一些转换类型的操作

mrc 模拟 arc

block中的copy

ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:

1.block作为函数返回值时

SLBlock myblock(){

    int age = 10;

    return ^{

        NSLog(@"------%d",age);

    }

}

终端打印结果图:

block作为返回值

注意:如果没有age1变量的话,因为没有访问auto变量,会当做全局变量处理,打印出来是global类型

2.将block赋值给__strong指针时

int main(int argc,const char* argv[]) {

    @autoreleasepool {

        int age =10;

        SLBlock block = ^{//默认是strong指针引用

            NSLog(@"---------%d", age);

        };

        NSLog(@"%@", [block class]);

 }

    NSLog(@"%@", [block class]);

}

如果将上述的代码更改成

int age = 10

NSLog(@"%@",[^{NSLog(@"----------%d",age);} class]);

这个时候这个打印应该是stack,因为没有strong指针引用

3.block作为Cocoa API中方法名含有usingBlock的方法参数时

4.block作为GCD API的方法参数时

MRC下block属性的建议写法

@property (copy, nonatomic) void(^block)(void);

ARC下block属性的建议写法

@property (strong, nonatomic) void(^block)(void);

@property (copy, nonatomic) void(^block)(void);

__blcok 修饰符

__block 可以用于解决block内部无法修改auto变量值的问题,__block不能修饰全局变量,静态变量(static),编译器会将__block变量包装成一个对象

__block 声明变量图 auto 声明变量图

__block 的内存管理

当block在栈上,并不会对__block变量产生强引用,当block被copy到堆上时,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对_block变量形成强引用(retain),如下图所示:

block copy 到堆中强引用图

当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose 函数会自动释放引用的_block变量(release),如下图所示:

block dispose图

从clang出来的源码中,我们可以发现里面__block修饰的会有一个__forwarding指针,这个指针是干嘛用的呢?然而这里__forwarding指针真的永远指向自己么?我们来做一个实验

MRC 

可以看到MRC环境下,var->forwarding->var(age的指针)是相同的,说明栈中的block并没有拷贝到堆中,并且此时的forwarding指向的就是自己栈上block的内存地址,再看一下这个情形:

MRC ARC

可以看到MRC下block使用copy,和ARC下直接定义是一样的情况,说明ARC下,自动帮我们将strong 引用的block拷贝到了堆中,这个时候,可以发现两个age的地址值并不一样,由此可以说明_forwarding 指针指向的不是同一个并且由打印结果age的值可以看到,block里面age的值和block外age的值是一样,都是30,所以在使用__block之后,在栈中访问age的时候,val->forwarding->val 访问的也是堆中的age值,在堆中访问age的时候,val->forwarding->val 访问的是堆中的age值,那这个说明了forwarding是为了让我们更好的管理内存的,不论现在block是出于栈中还是堆中,都不会影响到寻找到的相关信息,当block是在栈中,__forwarding指向的就是栈本身的地址,当block copy到堆中的时候,__forwarding指针指向的就是堆本身的地址,如下图所示:

__forwarding指针

上面中有没有人存在一个这样的问题,就是age的地址值,到底是__Block_byref_age_0 *age 的地址值,还是__Block_byref_age_0 *age 里面的变量age的地址值呢?我们来证明一下:

实例图 证明图

__weak问题

在使用clang转换OC为C++代码时,可能会遇到以下问题

cannot create __weak reference in file using manual reference

解决方案:支持ARC,指定运行时系统版本,比如:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main.cpp

循环引用

例子1:

测试例子 打印结果

可以看到在打印之前,person因为出了作用域,应该被销毁才对的,但是这里斌没有执行dealloc操作,说明person没有被释放,内存泄漏了,我们来分析一下内存分布图:

例子1内存分布图

可以看到运行到大括号外面的时候,箭头1销毁,但是这个时候2,3还是相互引用,导致不能释放,所以出现了循环引用,这个时候应该怎么解决呢?直接让其中一根线是弱引用就可以了,3弱引用也可以,2弱引用也可以,因为这里block是person的一个属性,所以想person销毁的时候,block就销毁,那么我这里就是让block里面的引用弄成弱引用就可以了,例如下图所示:

弱引用

相应的代码更改成为:

代码改造图 打印结果图

大括号结束后,引用1销毁,person没有强引用指向,person销毁,person销毁之后,因为3是若引用,所以block销毁,内存循环应用解除,这里除了用__weak 之外,使用__unsafe__unretained也不会产生强引用,但是不安全,指向的对象销毁之后,指针存储的地址不变,再次访问的时候会导致野指针,__weak会赋值成nil,安全;当然通过__block 也可以,具体可以看下面的补充要点;

补充要点

1.block的属性修饰词为什么是copy?使用block有哪些使用注意?

block一旦没有进行copy操作,就不会在堆上,需要注意循环引用问题,例如下图展示:

循环引用图

使用ARC解决循环引用

arc 解决循环引用

使用MRC解决循环引用

MRC解决循环引用

MRC情况下,如果不加__unsafe_unretained ,或者__block 修饰的话,如果block使用了copy拷贝到堆上的时候,因为声明的变量是强引用,在内部会对变量使用retain一次,这样最后就会导致强引用了,也就是会循环引用

可以添加微信一起交流学习:fslskz

相关文章

  • iOS开发之Block原理探究

    Block概述 Block本质 Block调用 Block分类 Block循环引用 Block原理探究 Block...

  • block底层原理探究(二):内存管理

    前篇block底层原理探究(一):捕获,我们探究了block捕获外部变量的原理;如果block捕获的是对象类型的a...

  • Block 探究

    Block是OSX Snow Leopard和iOS 4引入的C语言扩充功能,是一个带有自动变量(局部变量)的匿名...

  • 探究 Block

    一、了解 Block先从一个简单的需求来说:传入两个数,并且计算这两个数的和,为此创建了这样一个block : 这...

  • Block探究

    使用clang -rewrite-objc main.m,得到编译后的c++源码,可以了解到 block也是一个结...

  • Block探究

    block的实质是什么?一共有几种block?都是什么情况下生成的? block的实质是什么? block本质上也...

  • Block探究

    block的原理是怎样?本质是什么? block本质上也是一个OC对象,block是封装了函数调用与及调用环境的O...

  • Block探究

    在我们实际的开发过程中,block的使用可以说是经常遇到到的了吧,GCD,网络请求,动画都随处可见block的影子...

  • block本质

    探究block本质fishhookHook Objective-C Block with Libffi如何使用li...

  • 关于block(4)

    关于block(4) 标签: iOS 技术 接上篇,我们继续探究block。 block的copy属性 研究到这里...

网友评论

      本文标题:Block探究

      本文链接:https://www.haomeiwen.com/subject/mwqcoqtx.html