Block

作者: 纳兰沫 | 来源:发表于2019-01-24 15:13 被阅读61次

参考文献

Block本质
Block由浅入深(5):三种类型的Block
Block由浅入深(3):Block捕获局部变量
iOS基础:block 内如何修改 block 外部变量
Block原理,为什么block能捕获变量 -- 原理篇
Block原理,为什么block能捕获变量 -- 实战篇

block在内部会作为一个指向结构体的指针 当调用block的时候其实就是根据
block对应的指针找到相应的函数  进而进行调用 并传入自身
block本质上是一个OC对象 它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象
block是封装函数及其上下文的OC对象
block底层结构图
image.png
self是当调用block函数的参数 参数是局部变量 self指向调用者

block捕获局部变量的本质

block体内的变量与被block引用的外部局部变量是2个不同的变量 它们有不同
的作用域 有不同的存储空间 它们的值早在block实现时就已经确定好了,而不
是在block执行时才被确定的

block为什么不可以修改局部变量

局部变量:
__block_impl_0 实现中会增加一个成员变量,用来存储 block 外部变量的
值,仅仅是一次值传递,在 block 内部对其操作会报错。
全局变量:
全局变量在 __block_impl_0 实现中并没有出现,因为全局变量存储在静态
数据区,程序结束前并不会被销毁,block 可直接访问,因此在 
__block_impl_0 结构体中没有体现。
局部静态变量:
__block_impl_0 结构体中会增加一个指针变量,在 _block_func_0 中通过
局部变量的地址可以对其进行访问修改,但其作用域就是当前所在函数的作用
域

block的类型

__NSGlobalBlock__ (_NSConcreteGlobalBlock)
__NSStackBlock__ (_NSConcreteStackBlock)
__NSMallocBlock__(__NSConcreteMallocBlock)
image.png
如何判断block是哪种类型
- 没有访问auto变量的block 是__NSGlobalBlock__ 放在数据段
- 访问了auto变量的block是___NSStackBlock__
- [NSStackBlock __copy] 操作就变成了__NSMallocBlock__
block作为返回值时或者赋值给一个strong/copy修饰的对象会自动调用copy

MRC下手动调用copy 也会到达堆区 MRC下赋值不会触发copy操作 所以 MRC 下不手动调copy就是_NSConcreteStackBlock
在ARC下 block作为返回值或者赋值给一个strong/copy修饰的对象会自动调用copy 所以 ARC下大多数都是_NSConcreteMallocBlock

__block修饰符作用

1.__block可以用于解决block内部无法修改auto变量值的问题
2.__block不能修饰全局变量 静态变量
3.编译器会将__block变量包装成一个对象
4.__block修改变量 age->__forwarding->age
5.__Block_byref_age_0结构体内部地址和外部变量age是同一个地址

_block的实现原理

_block将变量包装成对象 然后在把捕获到的变量封装到block的结构体里面 
block内部存储的变量为结构体指针 也就可以通过指针找到内存地址进而修改
变量的值

_block修饰变量或者对象对象类型的变量都是直接strong 而对象的引用类型 取决于block捕获的对象类型的变量

block的属性修饰词为什么是copy

block一旦没有进行copy操作 就不会在堆上
block在堆上 程序员就可以对block做内存管理等操作 可以控制block的生命
周期

当block被copy到堆时 对__block修饰的变量做了什么

1.会调用block内部的copy函数
2.copy函数内部会调用__Block_object_assign函数
3._block_object_assign函数会对__block变量形成强引用
4.对__block修饰的变量assign函数对其强引用 对于外部对象assig函数根据
外部如何引用而引用
block0复制到堆上
block1复制到堆上

当block从堆上移除时 对__block修饰的变量做了什么

1.会调用block内部的dispose函数
2.dispose函数内部会调用__Block_object_dispose函数
3.__Block_object_dispose函数会自动释放引用的_block变量(release)
block0被废弃
block1被废弃

block对象类型的auto变量 __block变量的区别

1.当block在栈上时 对他们都不会产生强引用
2.当block拷贝到堆上时
  - 都会通过copy函数来处理它们
  - 对于_block修饰的变量assign函数对其强引用 对于外部变量assign函数
    根据外部变量如何引用而引用
3.当block从堆上移除时
  - 都会通过dispose函数来释放它们
4.__block的__forwarding指针
  - 栈上__block的__forwarding指向本身
  - 栈上__block复制到堆上后 栈上block的__forwarding指向堆上block 
    堆上的block的_forwarding指向本身
_forwarding指向

arc下如何解决block循环引用的问题

三种方式 __weak __unsafe_unretained _block

第一种方式 __weak
Person *person = [[Person alloc] init];
//        __weak Person *weakPerson = person;
__weak typeof(person) weakPerson = person;

person.block = ^{
    NSLog(@"age is %d", weakPerson.age);
};
第二种方式:__unsafe_unretained
__unsafe_unretained Person *person = [[Person alloc] init];
person.block = ^{
    NSLog(@"age is %d", weakPerson.age);
};
第三种方式:__block
__block Person *person = [[Person alloc] init];
person.block = ^{
    NSLog(@"age is %d", person.age);
    person = nil;
};
person.block();
三种方式比较
1.__weak 不会产生强引用 指向的对象销毁时 会自动让指针置nil
2.__unsafe_unretained 不会产生强引用 不安全 指向的对象销毁时 指针
存储的地址值不变
3.__block 必须吧引用对象置为nil 并且要调用该block
__weak和__unsafe_unretained解决循环引用的方式
_block解决循环引用方式

MRC下解决block循环引用的问题

两种方式 __unsafe_unretained __block

第一种方式__unsafe_unretained
__unsafe_unretained Person *person = [[Person alloc] init];
person.block = ^{
    NSLog(@"age is %d", weakPerson.age);
};
第二种方式:__block
__block Person *person = [[Person alloc] init];
person.block = ^{
    NSLog(@"age is %d", person.age);
};

C语言变量

- 自动变量
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量

在block中改变变量值的方式

1.传递内存地址指针到block中
2.改变存储区方式

应用程序内存分配

image

系统默认调用copy方法把block复制的情况

1.手动调用copy
2.block是函数的返回值
3.block被强引用 block被赋值给_strong或者id类型
4.调用系统API入参中含有usingblock的方法

copy函数把block从栈上拷贝到堆上 dispose函数是把堆上的函数在废弃的时候销毁掉 堆空间的block自己销毁之后也会对持有的对象进行release操作

_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结构体的
捕获对象是什么类型的指针,对捕获对象产生强引用或者弱引用。可以理解为
 _Block_object_assign函数内部会对捕获的对象进行引用计数器的操作,如
果__main_block_impl_0结构体内捕获的对象指针是__strong类型,则为强
引用,引用计数+1,如果__main_block_impl_0结构体内对象指针是__weak
类型,则为弱引用,引用计数不变

_Block_object_dispose函数调用时机及作用

1).当block从堆中移除时就会自动调用__main_block_desc_0中的
__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用
_Block_object_dispose函数。
2)._Block_object_dispose会对捕获对象做释放操作,类似于release,也
就是断开对person对象的引用,而person究竟是否被释放还是取决于person
对象自己的引用计数。

总结Block的调用

1).一旦block中捕获的变量为对象类型,block结构体中的
__main_block_desc_0会出两个参数copy和dispose。因为访问的是个对象,
block希望拥有这个对象,就需要对对象进行引用,也就是进行内存管理的操作
比如说对对象进行retain操作,因此一旦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变量 , 也会调用对象类型的dispose , 对象的引用计数
减1

相关文章

  • iOS开发之Block原理探究

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

  • block的使用

    定义block 返回类型 (^block名称)(参数) = ^(){block内容}; 调用block block...

  • Block 02 - __block

    Block 02 - __block __block 的作用 __block 可以解决 Block 内部无法修改 ...

  • iOS面试之Block大全

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS面试之Block模块

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS Block

    Block的分类 Block有三种类型:全局Block,堆区Block,栈区Block 全局Block 当Bloc...

  • iOS block 为什么官方文档建议用 copy 修饰

    一、block 的三种类型block 三种类型:全局 block,堆 block、栈 block。全局 block...

  • iOS开发block是用copy修饰还是strong

    Block分为全局Block、堆Block和栈Block1、在定义block没有引用外部变量的时候,block为全...

  • block 初探

    全局block, 栈block, 堆block

  • Block

    一、Block本质 二、 BlocK截获变量 三、__block 修饰变量 四、Block内存管理 五、Block...

网友评论

    本文标题:Block

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