几个关于block的问题
- block的内部实现,结构体是什么样的
- block是类吗,有哪些类型
- 一个int变量被 __block 修饰与否的区别?block的变量截获
- block在修改NSMutableArray,需不需要添加__block
- 怎么进行内存管理的
- block可以用strong修饰吗
- 解决循环引用时为什么要用__strong、__weak修饰
- block发生copy时机
- Block访问对象类型的auto变量时,在ARC和MRC下有什么区别
block、函数指针、闭包
- 和C语言的函数指针的定义对比
1 | // Block |
例如输入和返回参数都是字符串:
1 | (char *) (*c_func)(const char *); |
- 闭包
闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称作自由变量)。
block实际上就是Objcective-C对于闭包的实现
block的内部实现,数据结构是什么样的?
1 | int age = 20; |
通过clang编译成cpp文件xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
- block结构
1 | int age = 20; |
删除掉一些强制转换的代码
1 | int age = 20; |
block
本质就是一个结构体对象,结构体__main_block_imp_0
结构如下
1 | struct __main_block_impl_0 { |
结构体中第一个变量是struct __block_impl impl
1 | struct __block_impl { |
结构体中第二个变量是__main_block_desc_0
;
1 | static struct __main_block_desc_0 { |
block数据类型
block中有isa
指针,block是一个OC
对象
1 | void (^block)(void) = ^{ |
输出结果
1 | iOS-block[18429:234959] block.class = __NSGlobalBlock__ |
继承关系可以表示为__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
小结
- isa:由此可知,block也是一个对象类型,具体类型包括
_NSConcreteGlobalBlock
、_NSConcreteStackBlock
、_NSConcreteMallocBlock
- block在Clang编译器前端得到实现,可以生成C中间代码。
- 实际上block就是指向结构体的指针
- block本质上也是一个OC对象,它内部也有isa指针,block是封装了函数调用以及函数调用环境的OC对象
一个int变量被 __block 修饰与否的区别?block的变量截获?
没有被__block
修饰的int,block体中对这个变量的引用是值拷贝
通过__block
修饰的int,block体中对这个变量的引用是指针拷贝
变量捕获
auto
修饰的局部变量,离开作用域就销毁了,指针传递的话,可能导致访问的时候,该变量已经销毁了,程序就会出问题,而全局变量本来就是在哪里都可以访问的,所以无需捕获。
block怎么进行内存管理的?
block按照内存分布,分三种类型:全局内存中的block、栈内存中的block、堆内存中的block。
block的3种类型
可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
在MRC和ARC下block的分布情况
- MRC下:
当block内部引用全局变量或者不引用任何外部变量时,该block是在全局内存中的。
当block内部引用了外部的非全局变量的时候,该block是在栈内存中的。
当栈中的block进行copy操作时,会将block拷贝到堆内存中。
通过__block修饰的变量,不会对其应用计数+1,不会造成循环引用。
- ARC下:
当block内部引用全局变量或者不引用任何外部变量时,该block是在全局内存中的。
当block内部引用了外部的非全局变量的时候,该block是在堆内存中的。
也就是说,ARC下只存在全局block和堆block
。
1 | 通过__block修饰的变量,在block内部依然会对其引用计数+1,可能会造成循环引用。 |
block可以用strong修饰吗?
在MRC
环境中,是不可以的,strong
修饰符会对修饰的变量进行retain
操作,这样并不会将栈中的block
拷贝到堆内存中,而执行的block
是在堆内存中,所以用strong
修饰的block
会导致在执行的时候因为错误的内存地址,导致闪退。
在ARC
环境中,是可以的,因为在ARC
环境中的block
只能在堆内存或全局内存中,因此不涉及到从栈拷贝到堆中的操作。
解决循环引用时为什么要用__strong、__weak修饰?
__weak
修饰的变量,不会出现引用计数+1,也就不会造成block强持有外部变量,这样也就不会出现循环引用的问题了。
但是,我们的block
内部执行的代码中,有可能是一个异步操作,或者延迟操作,此时引用的外部变量可能会变成nil,导致意想不到的问题,而我们在block内部通过__strong
修饰这个变量时,block会在执行过程中强持有这个变量,此时这个变量也就不会出现nil的情况,当block执行完成后,这个变量也就会随之释放了(自动变量,作用域只在block里)。
1 | block内部的strongSelf仅仅是个局部变量,存在栈中,会在block执行结束之后回收,不会再造成循环引用,并且会使页面返回上一级 |
block发生copy时机?
一般情况在ARC环境中,编译器将创建在栈中的block会自动拷贝到堆内存中,而block作为方法或函数的参数传递时,编译器不会做copy操作。如下情况,编译器会自动copy
- block作为方法或函数的返回值时,编译器会自动完成copy操作。
- 当block赋值给通过strong或copy修饰的id或block类型的成员变量时。
- 当 block 作为参数被传入方法名带有
usingBlock
的Cocoa Framework
方法或GCD
的 API 时。
Block访问对象类型的auto变量时,在ARC和MRC下有什么区别?
首先我们知道,在ARC下,栈区创建的block会自动copy到堆区;而MRC下,就不会自动拷贝了,需要我们手动调用copy函数。
我们再说说block的copy操作,当block从栈区copy到堆区的过程中,也会对block内部访问的外部变量进行处理,它会调用Block_object_assign
函数对变量进行处理,根据外部变量是strong还会weak对block内部捕获的变量进行引用计数+1或-1,从而达到强引用或弱引用的作用。
因此
在ARC下,由于block被自动copy到了堆区,从而对外部的对象进行强引用,如果这个对象同样强引用这个block,就会形成循环引用。
在MRC下,由于访问的外部变量是auto修饰的,所以这个block属于栈区的,如果不对block手动进行copy操作,在运行完block的定义代码段后,block就会被释放,而由于没有进行copy操作,所以这个变量也不会经过Block_object_assign
处理,也就不会对变量强引用。
简单说就是:
ARC下会对这个对象强引用,MRC下不会