iOS开发中,block的使用还是挺频繁的,不管是自定义还是系统提供的很多block函数。但是抽丝剥茧的事还是发生在面试中,面试的时候block被提问的概率还是挺高的。
以下根据面试题做出对block部分知识点的讲解,仅代表个人看法,如果有错误,请指出。
看题之前,先了解一下block类型:
分NSConcreteGlobalBlock
NSConcreteStackBlock
NSConcreteMallocBlock分别在全局变量区,栈区,堆区。
_NSConcreteGlobalBlock类型的block要么是空block,要么是不访问任何外部变量的block。它既不在栈中,也不在堆中,我理解为它可能在内存的全局区。(配置在全局变量上的Block,从变量作用域外也可以通过指针安全地使用)
举例:
void(^ blockA)() = ^{
NSLog(@"just a block");
};
_NSConcreteStackBlock类型的block有闭包行为,也就是有访问外部变量,并且该block只且只有有一次执行,因为栈中的空间是可重复使用的,所以当栈中的block执行一次之后就被清除出栈了,所以无法多次使用。
举例:
下边这个例子,如果在ARC下创建,blockC会创建在堆上,如果在MRC 环境下创建会创建在栈上。
int value = 10;
void(^ blockC)() = ^{
NSLog(@"just a block === %d", value);
};
_NSConcreteMallocBlock类型的block有闭包行为,并且该block需要被多次执行。当需要多次执行时,就会把该block从栈中复制到堆中,供以多次执行。
举例:
void addBlockToArray(NSMutableArray *array) {
char b = 'B';
[array addObject:^{
printf("%c\n", b);
}];
}
void exampleB() {
NSMutableArray *array = [NSMutableArray array];
addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
addBlockToArray中的block还在栈区,exampleB中的block被复制到了堆区变成了NSConcreteMallocBlock。
提问的第一个问题如下:
1,下面代码在按钮点击后,在ARC下会发生什么,MRC下呢?为什么?
@property(nonatomic, assign) void(^block)();
- (void)viewDidLoad {
[superviewDidLoad];
int value = 10;
void(^blockC)() = ^{
NSLog(@"just a block === %d", value);
};
NSLog(@"%@", blockC);
_block = blockC;
}
- (IBAction)action:(id)sender {
NSLog(@"%@", _block);
}
经测试,在ARC环境下是不会崩溃的,在MRC环境下因为访问已经释放的对象,程序崩溃。个人给出的解释是,在ARC环境下,创建的blockC ,blockC是在堆区,MRC环境下blockC是在栈区,栈区在函数返回以后就销毁,再次访问的时候就会引起访问已经销毁的对象。
注:此处之前搜到的答案是这样的:@property(nonatomic, assign) void(^block)(); 在ARC环境下,不管用assign,copy还是strong来修饰block都会被copy到堆区,所以block不会因为函数的返回而销毁。在MRC环境下必须用copy然后调用点语法赋值(self.block = blockC),block 就会从栈区copy到堆区。
但是实际测试结果如下:
int a =0;
self.block=^{
NSLog(@"aaa%d",a);
};
NSLog(@"aaa");
结果显示
显示在栈区.png
所以个人认为,上边block在ARC环境下没有销毁,是因为blockC在堆区,而不是说ARC环境下assign修饰的block被copy到了堆区。
因此不管在MRC 还是ARC 定义成属性的block要用copy防止过早销毁。
2,在ARC环境下这段代码为什么不会崩溃?
@property(nonatomic, weak) void(^block)();
- (void)viewDidLoad {
[super viewDidLoad];
void(^ __weak blockA)() = ^{
NSLog(@"just a block");
};
_block = blockA;
}
- (IBAction)action:(id)sender {
_block();
}
经过测试blockA是在全局变量区,类型是NSConcreteGlobalBlock前边注意到(配置在全局变量上的Block,从变量作用域外也可以通过指针安全地使用)。
在全局变量区.png在MRC环境直接写报错,需要将 _weak做处理,不做深究。
注:在第一题中我们发现,直接创建的block是在堆区的,但是经过__weak修饰后会放在栈区。
下面代码中为什么可以直接用self?
[UIView animateWithDuration:1 animations:^{
self.view.backgroundColor = [UIColor yellowColor];
}];
下面这段代码可以用self吗?为什么?
- (void)doSomething {
[BlockClass doSomethingUseBlock:^{
NSLog(@"%@", self);
}];
}
关于第一个问题,我们会发现,在很多情况下,block中使用self不会引起循环引用问题,这首先,我们要搞明白什么是循环引用,就是当前类持有强引用这个block,然后在block中又强引用了当前类,彼此等待都不能销毁。但是UIView是一个类,当前控制器不可能强引用一个类,所以当前控制器没有强引用这个block,循环不成立。(第二个问题也就回答了)
此处拓展,在AFN中也是在block中使用self,他是进行了特殊处理,原理可以自己去搜一下。系统GCD是在结束的时候将对象都进行了释放。
以下补充知识点:
补充1. 关于block中变量修改引起的思考。
我们知道block中是不能直接修改外部变量的,必须经过_block修饰。原因是:block不允许修改的是栈中指针的内存地址,__block的作用是将栈中的地址放到堆中,这样就可以修改了。
补充2. 关于block中使用weak和strong修饰词
首先我们知道,为了防止循环引用,我们会使用weak来修饰self,防止产生强引用,但是在很多框架中我们会发现,block中还会有strong修饰词,这是防止block还在执行的时候,别的地方把self给释放了。找一个别人写好的例子
第一步:我们自定义一个类,在该类dealloc方法中加一行打印语句;
@interface SampleObject :NSObject
@end
@implementation SampleObject
- (void)dealloc{
NSLog(@"dealloc %@",[self class]);
}
@end
第二步:实例化该类,并在block中调用它;(没有加strong修饰符,三秒后释放该对象)
SampleObject* sample = [[SampleObject alloc]init];
self->sample= sample;
__weakSampleObject* weaksample = self->sample;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
NSIntegercount =0;
//__strong SampleObject* strongsample = weaksample;
while(count<10) {
count++;
NSLog(@"aaa %@",weaksample);
sleep(1);
}
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)),dispatch_get_main_queue(), ^{
self->sample=nil;
});
打印结果如下(没有用strong修饰符的打印结果如下):
输出.png结论是:如果仅仅使用__weak去修饰变量,当别处把变量释放后,block中该变量也会被释放掉。
那么好,我们在把第二步中的方法修改一下,加上strong修饰符:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
__strongSampleObject* strongsample = weaksample;
NSIntegercount =0;
while(count<10) {
count++;
NSLog(@"aaa %@",strongsample);
sleep(1);
}
});
打印结果如下:
加strong输出.png
结论是当加上修饰符strong时,当别处把变量释放掉,但调用该变量的block如果仍然没有执行结束,那么系统就会等待block执行完成后再释放,对该变量在block中的使用起到了保护作用。当block执行结束后会自动释放掉。
继续整理补充,,,,
网友评论