1、block
的内部实现,结构体是什么样的?
block的结构体如下:
struct Block_literal_1 {
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 {
unsigned long int reserved; // NULL
unsigned long int size; // sizeof(struct Block_literal_1)
// optional helper functions
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
void (*dispose_helper)(void *src); // IFF (1<<25)
// required ABI.2010.3.16
const char *signature; // IFF (1<<30)
} *descriptor;
// imported variables
};
isa:由此可知,block也是一个对象类型,具体类型包括_NSConcreteGlobalBlock、_NSConcreteStackBlock、_NSConcreteMallocBlock。
flags:block 的负载信息(引用计数和类型信息),按位存储,也可以获取block版本兼容的相关信息。以下是flags按bit位取与的所有可能值:
enum {
// Set to true on blocks that have captures (and thus are not true
// global blocks) but are known not to escape for various other
// reasons. For backward compatibility with old runtimes, whenever
// BLOCK_IS_NOESCAPE is set, BLOCK_IS_GLOBAL is set too. Copying a
// non-escaping block returns the original block and releasing such a
// block is a no-op, which is exactly how global blocks are handled.
BLOCK_IS_NOESCAPE = (1 << 23),
BLOCK_HAS_COPY_DISPOSE = (1 << 25),
BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code
BLOCK_IS_GLOBAL = (1 << 28),
BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30),
};
switch (flags & (3<<29)) {
case (0<<29): 10.6.ABI, no signature field available
case (1<<29): 10.6.ABI, no signature field available
case (2<<29): ABI.2010.3.16, regular calling convention, presence of signature field
case (3<<29): ABI.2010.3.16, stret calling convention, presence of signature field,
}
由此可知:当flags & (3<<29) is BLOCK_HAS_COPY_DISPOSE的时候,才会有copy_helper和dispose_helper函数指针。
invoke:是block具体实现函数指针地址,可以通过此地址直接调用block。
Block_descriptor_1:block的描述文内容,它包括如下:
size:block所占的内存大小
copy_helper:copy函数指针(不同版本不一定存在)
dispose_helper:dispose函数指针(不同版本不一定存在)
signature:block的实现函数的签名(不同版本不一定存在),可以通过此指针获取block的参数内容描述、返回值内容描述等
获取block的方法签名,可以参考这篇文章
2、block是类吗,有哪些类型?
从block的结构体中可知,block同样也有一个isa指针,所以block也是一个类,它的类型包括:
- _NSConcreteGlobalBlock
- _NSConcreteStackBlock
- _NSConcreteMallocBlock
3、一个int
变量被 __block
修饰与否的区别?block的变量截获?
没有被__block修饰的int,block体中对这个变量的引用是值拷贝,在block中是不能被修改的。
通过__block修饰的int,block体中对这个变量的引用是指针拷贝,它会生成一个结构体,复制这个变量的指针引用,从而达到可以修改变量的作用。
关于block的变量截获:
block会将block体内引用外部变量的变量进行拷贝,将其拷贝到block的数据结构中,从而可以在block体内访问或修改外部变量。
外部变量未被__block修饰时,block数据结构中捕获的是外部变量的值,通过__block修饰时,则捕获的是对外部变量的指针引用。
注意:block内部访问全局变量时,全局变量不会被捕获到block数据结构中。
举个栗子:
未被__block修饰的情况
int param = 1;
int a = param; // 没用__block修饰的时候,block内部捕获的外部变量
[self updateInt:a];
NSLog(@"----:%@", @(param));// 这里输出:1
// 没用__block修饰的时候,block内部实现如下
- (void)updateInt:(int)a{
a = 2;// 此时对外部变量修改是无效的
}
被__block修饰的情况
int param = 1;
int *a = ¶m; // 用__block修饰的时候,block内部捕获的外部变量,是外部变量的指针
[self updateInt:a];
NSLog(@"----:%@", @(param));// 这里输出:2
// 用__block修饰的时候,block内部实现如下
- (void)updateInt:(int *)a{
*a = 2;// 此时对外部变量修改是有效的
}
参考这篇文章
4、block
在修改NSMutableArray
,需不需要添加__block?
- 如果修改的是NSMutableArray的存储内容的话,是不需要添加__block修饰的。
- 如果修改的是NSMutableArray对象的本身,那必须添加__block修饰。
参考block的变量捕获。
5、block怎么进行内存管理的?
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修饰的变量,在block内部不会对其引用计数+1,不会造成循环引用。
参考这篇文章
6、block
可以用strong
修饰吗?
在MRC环境中,是不可以的,strong修饰符会对修饰的变量进行retain操作,这样并不会将栈中的block拷贝到堆内存中,而执行的block是在堆内存中,所以用strong修饰的block会导致在执行的时候因为错误的内存地址,导致闪退。
在ARC环境中,是可以的,因为在ARC环境中的block只能在堆内存或全局内存中,因此不涉及到从栈拷贝到堆中的操作。
7、解决循环引用时为什么要用__strong、__weak
修饰?
__weak修饰的变量,不会出现引用计数+1,也就不会造成block强持有外部变量,这样也就不会出现循环引用的问题了。
但是,我们的block内部执行的代码中,有可能是一个异步操作,或者延迟操作,此时引用的外部变量可能会变成nil,导致意想不到的问题,而我们在block内部通过__strong修饰这个变量时,block会在执行过程中强持有这个变量,此时这个变量也就不会出现nil的情况,当block执行完成后,这个变量也就会随之释放了。
8、block
发生copy
时机?
一般情况在ARC环境中,编译器将创建在栈中的block会自动拷贝到堆内存中,而block作为方法或函数的参数传递时,编译器不会做copy操作。
- block作为方法或函数的返回值时,编译器会自动完成copy操作。
- 当block赋值给通过strong或copy修饰的id或block类型的成员变量时。
- 当 block 作为参数被传入方法名带有
usingBlock
的 Cocoa Framework 方法或 GCD 的 API 时。
9、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下不会。
参考这篇文章