最新iOS⾯试题之Block+答案
Block
这⼀篇我们来研究⼀下objc的block并回答⼀下⾯试中的下列问题:
1. block的内部实现,结构体是什么样的
2. block是类吗,有哪些类型
3. ⼀个int变量被 __block 修饰与否的区别?block的变量截获
4. block在修改NSMutableArray,需不需要添加__block
5. 怎么进⾏内存管理的
6. block可以⽤strong修饰吗
7. 解决循环引⽤时为什么要⽤__strong、__weak修饰
8. block发⽣copy时机
9. Block访问对象类型的auto变量时,在ARC和MRC下有什么区别
在回答所有问题之前我们需要了解⼀些block背景相关的知识. 如下:
如何查看Block的内部实现,也就是说转换成背后真正的c/c++代码的block是什么样的?以及转换格式或者原理等.
-关于变量的作⽤域
Objective-C 转 C++的⽅法
下⾯我写了个⽰例TestClass.m类其中block代码如下
OC代码:
@interface TestClass ()
@end
@implementation TestClass
-(void)testMethods {
void (^blockA)(int a)=^(int a){
NSLog(@"%d",a);
};
if(blockA){
blockA(1990);
}
}
@end
经过上述转换操作我们在TestClass.cpp中最下⾯发现如下代码
C++代码
// @interface TestClass ()
/
* @end */
// @implementation TestClass
struct __TestClass__testMethods_block_impl_0 {
struct __block_impl impl;
struct __TestClass__testMethods_block_desc_0* Desc;
__TestClass__testMethods_block_impl_0(void *fp, struct __TestClass__testMethods_block_desc_0 *desc, int flags=0){
impl.isa =&_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __TestClass__testMethods_block_func_0(struct __TestClass__testMethods_block_impl_0 *__clf, int a){
NSLog((NSString *)&__NSConstantStringImpl__var_folders_wx_b8tcry0j24dbhr7zlzjq3v340000gn_T_TestClass_ee18d3_mi_0,a);
}
static struct __TestClass__testMethods_block_desc_0 {
size_t rerved;
size_t Block_size;
} __TestClass__testMethods_block_desc_0_DATA ={0,sizeof(struct __TestClass__testMethods_block_impl_0)};
static void _I_TestClass_testMethods(TestClass * lf, SEL _cmd){
void (*blockA)(int a)=((void (*)(int))&__TestClass__testMethods_block_impl_0((void *)__TestClass__testMethods_block_func_0,&__TestClass__testM ethods_block_desc_0_DATA));
if(blockA){
((void (*)(__block_impl *, int))((__block_impl *)blockA)->FuncPtr)((__block_impl *)blockA,1990);
}
}
上⾯的代码⽣成是通过如下操作:
打开终端,cd到TestClass.m所在⽂件夹,使⽤如下命令
clang -rewrite-objc TestClass.m
就会在当前⽂件夹内⾃动⽣成对应的TestClass.cpp⽂件
注意: 如果提⽰clang没有的话 需要安装, 输⼊如下
brew install clang-format
或者
brew link clang-forma
然后输⼊下⾯命令测试是否好使
clang-format --help
通过上述代码我们发现Block的其实是⼀个结构体类型
底层实现 会根据 __类名__⽅法名_block_impl_下标 (0代表这个⽅法或者这个类中第0个block 下⾯如果还有将会 第1个block 第2个…) struct __类名__⽅法名_block_impl_下标
关于变量的作⽤域
c语⾔的函数中可能使⽤的参数变量种类
参数类型
⾃动变量(局部变量)
静态变量(静态局部变量)
静态全局变量
全局变量
由于存储区域特殊,这其中有三种变量是可以在任何时候以任何状态调⽤的.
静态变量
静态全局变量
全局变量
⽽其他两种,则是有各⾃相应的作⽤域,超过作⽤域后,会被销毁.
1.block的内部实现,结构体是什么样的
看了上⾯的背景知识我们来回到⼀下这个问题
block的内部实现如下:
struct __TestClass__testMethods_block_impl_0 {
struct __block_impl impl;//成员变量
struct __TestClass__testMethods_block_desc_0* Desc;//desc 结构体声明
// 构造函数
// fp 函数指针
// desc 静态全局变量初始化的 __main_block_desc_ 结构体实例指针
// flags block 的负载信息(引⽤计数和类型信息),按位存储.
__TestClass__testMethods_block_impl_0(void *fp, struct __TestClass__testMethods_block_desc_0 *desc, int flags=0){
impl.isa =&_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//将来被调⽤的block内部的代码:block值被转换为C的函数代码
//这⾥,*__clf 是指向Block的值的指针,也就相当于是Block的值它⾃⼰(相当于C++⾥的this,
OC⾥的lf)
//__clf 是指向__TestClass__testMethods_block_impl_0结构体实现的指针
//Block结构体就是__TestClass__testMethods_block_impl_0结构体.Block的值就是通过__TestClass__testMethods_block_impl_0构造出来的
static void __TestClass__testMethods_block_func_0(struct __TestClass__testMethods_block_impl_0 *__clf, int a){
NSLog((NSString *)&__NSConstantStringImpl__var_folders_wx_b8tcry0j24dbhr7zlzjq3v340000gn_T_TestClass_9f58f7_mi_0,a);
}
static struct __TestClass__testMethods_block_desc_0 {
size_t rerved;
size_t Block_size;
} __TestClass__testMethods_block_desc_0_DATA ={0,sizeof(struct __TestClass__testMethods_block_impl_0)};
static void _I_TestClass_testMethods(TestClass * lf, SEL _cmd){
void (*blockA)(int a)=((void (*)(int))&__TestClass__testMethods_block_impl_0((void *)__TestClass__testMethods_block_func_0,&__TestClass__testM ethods_block_desc_0_DATA));
if(blockA){
((void (*)(__block_impl *, int))((__block_impl *)blockA)->FuncPtr)((__block_impl *)blockA,1990);
}
}
可以看得出来__TestClass__testMethods_block_impl_0有3个部分组成
impl 函数指针指向__TestClass__testMethods_block_impl_0
struct __block_impl {
void *isa;
int Flags;
int Rerved;//今后版本升级所需的区域
void *FuncPtr;//函数指针
};
Desc 指向__TestClass__testMethods_block_impl_0的Desc指针,⽤于描述当前这个block的附加信息的,包括结构体的⼤⼩等等信息.
static struct __TestClass__testMethods_block_desc_0 {
size_t rerved;//今后升级版本所需区域
size_t Block_size;//block的⼤⼩
} __TestClass__testMethods_block_desc_0_DATA ={0,sizeof(struct __TestClass__testMethods_block_impl_0)};
__TestClass__testMethods_block_impl_0()构造函数,也就是该block的具体实现
__TestClass__testMethods_block_impl_0(void *fp, struct __TestClass__testMethods_block_desc_0 *desc, int flags=0){
impl.isa =&_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
此结构体中
isa指针保持这所属类的结构体的实例的指针.
struct __TestClass__testMethods_block_impl_0相当于Objective-C类对象的结构体
_NSConcreteStackBlock相当于Block的结构体实例,也就是说block其实就是Objective-C对于闭包的对象实现
讲到这⾥block的内部实现你看懂了吗?结构体是什么样的你记住了吗? 其实看着繁琐 细⼼观察代码会发现还是⽐较简单的.
2.block是类吗,有哪些类型?
block也算是个类,因为它有isa指针,block.isa的类型包括
_NSConcreteGlobalBlock 跟全局变量⼀样,设置在程序的数据区域(.data)中
_NSConcreteStackBlock栈上(前⾯讲的都是栈上的 block)
_NSConcreteMallocBlock 堆上
这个isa可以按位运算
3.⼀个int变量被 __block 修饰与否的区别?block的变量截获
被__block 修饰与否的区别
⽤⼀段⽰例代码来解答这个问题吧:
__block int a =10;
int b =20;
PrintTwoIntBlock block =^(){
a -=10;
printf("%d, %d\n",a,b);
};
block();//0 20
a +=20;
b +=30;
printf("%d, %d\n",a,b);//20 50
block();/1020
通过__block修饰int a,block体中对这个变量的引⽤是指针拷贝,它会作为block结构体构造参数传⼊到结构体中且复制这个变量的指针引⽤,从⽽达到可以修改变量的作⽤.
int b没有被__block修饰,block内部对b是值copy.所以在block内部修改b不影响外部b的变化.
block的变量截获
通过如下代码我们来观察要⼀下变量的捕获
blk_t blk;
{
id array =[NSMutableArray new];
blk =[^(id object){
[array addObject:object];
NSLog(@"array count = %ld",[array count]);
} copy];
}
blk([NSObject new]);
blk([NSObject new]);
blk([NSObject new]);
输出打印
block_demo[28963:1629127] array count =1
block_demo[28963:1629127] array count =2
block_demo[28963:1629127] array count =3
我们把上⾯的代码翻译成C++看下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array;//截获的对象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0):array(_array){
impl.isa =&_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在Objc中,C结构体⾥不能含有被__strong修饰的变量,因为编译器不知道应该何时初始化和废弃C结构体。但是Objc的运⾏时库能够准确把握Block从栈复制到堆,以及堆上的block被废弃的时机,在实现上是通过__TestClass__testMethods_block_copy_0函数和
__TestClass__testMethods_block_dispo_0函数进⾏的