1. block的内部实现,结构体是什么样的
block的本质
- block本质上也是一个oc对象,他内部也有一个isa指针。
- block是封装了函数调用以及函数调用环境的OC对象。
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void(^block)(int ,int) = ^(int a, int b){
NSLog(@"this is block,a = %d,b = %d",a,b);
NSLog(@"this is block,age = %d",age);
};
block(3,5);
}
return 0;
}
使用命令行将代码转化为c++查看其内部结构,与OC代码进行比较
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
// 定义block变量代码
void(*block)(int ,int) = ((void (*)(int, int))&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA, age)
);
block定义中调用了__main_block_impl_0
函数,并且将__main_block_impl_0函数的地址赋值给了block
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
//同名构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; //block代码块函数地址
Desc = desc; //block对象占用内存大小
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//block中的代码被封装成函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5__mh11h5tx6w17z4xt87qqj45h0000gn_T_main_34785e_mi_0,a,b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5__mh11h5tx6w17z4xt87qqj45h0000gn_T_main_34785e_mi_1,age);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; //存储着__main_block_impl_0的占用空间大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
__main_block_impl_0
结构体内可以发现__main_block_impl_0构造函数中传入了四个参数。(void *)__main_block_func_0
、&__main_block_desc_0_DATA
、age
、flags
。其中flage有默认值,也就说flage参数在调用的时候可以省略不传。而最后的 age(_age)则表示传入的_age参数会自动赋值给age成员,相当于age = _age。
block
块中的代码封装成__main_block_func_0函数,并将__main_block_func_0函数的地址传入了__main_block_impl_0的构造函数中保存在结构体内.
//调用block的代码
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 3, 5);
(__block_impl *)block
将block强制转化为__block_impl
类型的,因为__block_impl是__main_block_impl_0结构体的第一个成员
,相当于将__block_impl结构体的成员直接拿出来放在__main_block_impl_0中,那么也就说明__block_impl的内存地址就是__main_block_impl_0结构体的内存地址开头。所以可以转化成功。并找到FunPtr成员。
FunPtr中存储着通过代码块封装的函数地址,那么调用此函数,也就是会执行代码块中的代码。并且回头查看__main_block_func_0函数,可以发现第一个参数
就是__main_block_impl_0
类型的指针。也就是说将block传入__main_block_func_0函数中,便于重中取出block捕获的值
。
可以看看这篇文章
2. block是类吗,有哪些类型
从block的结构体中可知,block同样也有一个isa指针,所以block也是一个类,它的类型包括:
- _NSConcreteGlobalBlock
- _NSConcreteStackBlock
-
_NSConcreteMallocBlock
3. __block的底层原理
__block修饰符
-
__block
可以用于解决block内部无法修改auto变量值的问题 -
__block
不能修饰全局变量、静态变量(static) - 编译器会将
__block
变量包装成一个结构体,结构体有个__forwarding
指针指向这个结构体,通过__forwarding
修改其值。 - 引用
__block
变量的block在堆上时,__block变量也会copy到堆上,栈上__block变量的__forwarding
指向堆上的变量,堆上的__forwarding
指向自己,修改的其实是堆上的__block变量
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
4. 一个int变量被 __block 修饰与否的区别?block的变量截获
- 没有被__block修饰的int,block体中对这个变量的引用是
值拷贝
,在block中是不能被修改的。 - 通过__block修饰的int,block体中对这个变量的引用是
指针拷贝
,它会生成一个结构体,复制这个变量的指针引用,从而达到可以修改变量的作用。
关于block的变量截获:
block会将block体内引用外部变量的变量进行拷贝,将其拷贝到block的数据结构中,从而可以在block体内访问或修改外部变量。
外部变量未被__block修饰时,block数据结构中捕获的是外部变量的值,通过__block修饰时,则捕获的是对外部变量的指针引用。
注意:block内部访问全局变量时,全局变量不会被捕获到block数据结构中。
5. block在修改NSMutableArray,需不需要添加__block
- 如果修改的是NSMutableArray的存储内容的话,是不需要添加__block修饰的。
- 如果修改的是NSMutableArray对象的本身,那必须添加__block修饰。
6. __block 在 ARC 和 MRC 下含义一样吗?
MRC
环境下,block 截获外部用 __block 修饰的变量,不会
增加对象的引用计数
ARC
环境下,block 截获外部用 __block 修饰的变量,会
增加对象的引用计数
在 MRC
环境下,可以通过 __block
来打破循环引用
在 ARC
环境下,则需要用__weak、 __unsafe_unretained
来打破循环引用
7. 怎么进行内存管理的
block按照内存分布,分三种类型:
- 全局内存中的block
- 栈内存中的block
- 堆内存中的block。
在MRC和ARC下block的分布情况不一样
MRC下:
当block内部引用全局变量或者不引用任何外部变量时,该block是在全局内存中的。
当block内部引用了外部的非全局变量的时候,该block是在栈内存中的。
当栈中的block进行copy操作时,会将block拷贝到堆内存中。
通过__block
修饰的变量,不会对其应用计数+1,不会造成循环引用。
ARC下:
当block内部引用全局变量或者不引用任何外部变量时,该block是在全局内存中的。
当block内部引用了外部的非全局变量的时候,该block是在堆内存中的。
也就是说,ARC
下只存在全局block
和堆block
。
通过__block修饰的变量,在block内部依然会对其引用计数+1,可能会造成循环引用。
通过用__weak、__unsafe_unretained 修饰的变量,在block内部不会对其引用计数+1,不会造成循环引用。
8. block解决循环引用- ARC
方法一:用__weak、__unsafe_unretained 解决
__weak thpeof(self) weakSelf = self;
self.block = ^{
printf("%p", weakSelf);
};
__unsafe_unretained id weakSelf = self;
self.block = ^{
NSLog(@"%p", weakSelf);
}
方法二:用__block解决(必须要调用block)
__block id weakSelf = self;
self.block = ^{
printf("%p", weakSelf);
weakSelf = nil;
};
self.block();
9. 解决循环引用时为什么要用__strong、__weak修饰
__weak
修饰的变量,不会出现引用计数+1,也就不会造成block强持有外部变量,这样也就不会出现循环引用
的问题了。
但是,我们的block内部执行的代码中,有可能是一个异步操作
,或者延迟操作
,此时引用的外部变量可能会变成nil
,导致意想不到的问题,而我们在block内部通过__strong
修饰这个变量时,block会在执行过程中强持有这个变量,此时这个变量也就不会出现nil的情况,当block执行完成后,这个变量也就会随之释放了。
10. block发生copy时机
一般情况在ARC
环境中,编译器将创建在栈中的block会自动拷贝
到堆内存中,而block作为方法或函数
的参数
传递时,编译器不会
做copy操作。
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
(1)block作为函数返回值时
(2)将block赋值给__strong指针时
(3)block作为 Cocoa API 中,方法名里含有usingBlock的方法参数时
(4)block作为 GCD API 的方法参数时
11. Block访问对象类型的auto变量时,在ARC和MRC下有什么区别
在ARC
下,栈区创建的block会自动copy到堆区;
而MRC
下,就不会自动拷贝了,需要我们手动调用copy函数。
- 一旦block中捕获的变量为
对象类型
, block结构体中的__main_block_desc_0
会出现两个函数copy
和dispose
. 因为block内部会访问这个对象, block需要拥有这个对象,就需要对被捕获的对象进行强引用, 因此Block内部也对内存进行管理操作.因此一旦block捕捉到了变量的类型是对象类型, 就会生成copy和dispose来对内部引用的对象进行内存管理.
(1)如果block是在栈上,将不会对auto变量产生强引用
(2)如果block被拷贝到堆上- 如果block被拷贝到堆上, copy函数会调用
_Block_object_assign
, 该函数会根据auto变量的修饰符(__strong,__weak,unsafe_unretained
)来做出相应的操作, 行成强引用或者弱引用.- 如果block从堆中移除,dispose函数会调用
_Block_object_dispose
函数,自动释放引用的auto变量。
因此,在ARC
下,由于block被自动copy到了堆区,从而对外部的对象进行强引用
,如果这个对象同样强引用这个block,就会形成循环引用
。
在MRC
下,由于访问的外部变量是auto修饰的,所以这个block属于栈区
的,如果不对block手动进行copy操作,在运行完block的定义代码段后,block就会被释放,而由于没有进行copy操作,所以这个变量也不会经过Block_object_assign
处理,也就不会对变量强引用。
简单说就是:
ARC(堆block
)下会对这个对象强引用,MRC(栈block
)下不会。
12. 被__block修饰的对象类型
1、当__block变量在栈上时,不会对指向的对象产生强引用
2、当__block变量被copy到堆时
(1)会调用__block变量内部的copy函数
(2)copy函数内部会调用_Block_object_assign函数
(3)_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)作出相应操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain
)
3、如果__block变量从堆上移除
(1)会调用__block变量内部的dispose函数
(2)dispose函数内部会调用_Block_object_dispose函数
(3)_Block_object_dispose函数会自动释放指向的对象(release)